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 "DataMapper/Record.hpp"
19#include "SqlConnection.hpp"
20#include "SqlQuery.hpp"
21#include "SqlQueryFormatter.hpp"
22#include "SqlServerType.hpp"
23#include "TracyProfiler.hpp"
34#include <source_location>
51template <
typename QueryObject>
53 { queryObject.ToSql() } -> std::convertible_to<std::string>;
57class SqlVariantRowCursor;
91 [[nodiscard]] LIGHTWEIGHT_API
bool IsAlive() const noexcept;
94 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrepared() const noexcept;
97 [[nodiscard]] LIGHTWEIGHT_API
SqlConnection& Connection() noexcept;
100 [[nodiscard]] LIGHTWEIGHT_API
SqlConnection const& Connection() const noexcept;
103 [[nodiscard]] LIGHTWEIGHT_API
SqlErrorInfo LastError() const;
110 std::string_view
const& tableAlias)
const;
113 [[nodiscard]] LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept;
119 LIGHTWEIGHT_API
void Prepare(std::string_view query) &;
134 [[nodiscard]] std::
string const& PreparedQuery() const noexcept;
137 template <SqlInputParameterBinder Arg>
138 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg);
141 template <SqlInputParameterBinder Arg, typename ColumnName>
142 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint);
145 template <SqlInputParameterBinder... Args>
159 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
160 [[nodiscard]]
SqlResultCursor ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch,
161 MoreColumnBatches const&... moreColumnBatches);
171 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
172 [[nodiscard]]
SqlResultCursor ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch,
173 MoreColumnBatches const&... moreColumnBatches);
180 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
182 MoreColumnBatches const&... moreColumnBatches);
212 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
213 requires(sizeof...(ColumnAccessors) >= 1
214 && (std::invocable<ColumnAccessors const&, std::ranges::range_value_t<Rows> const&> && ...))
215 [[nodiscard]]
SqlResultCursor ExecuteBatch(Rows const& rows, ColumnAccessors const&... accessors);
219 ExecuteDirect(std::string_view const& query, std::source_location location = std::source_location::current());
223 std::source_location location = std::source_location::current());
237 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor ExecuteBatchFetch(std::string_view query, std::
size_t arrayDepth);
240 template <typename Callable>
242 void MigrateDirect(Callable const& callable, std::source_location location = std::source_location::current());
246 template <typename T>
248 [[nodiscard]] std::optional<T> ExecuteDirectScalar(std::string_view const& query,
249 std::source_location location = std::source_location::current());
252 template <typename T>
254 [[nodiscard]] T ExecuteDirectScalar(std::string_view const& query,
255 std::source_location location = std::source_location::current());
259 template <typename T>
261 [[nodiscard]] std::optional<T> ExecuteDirectScalar(
SqlQueryObject auto const& query,
262 std::source_location location = std::source_location::current());
265 template <typename T>
267 [[nodiscard]] T ExecuteDirectScalar(
SqlQueryObject auto const& query,
268 std::source_location location = std::source_location::current());
271 [[nodiscard]] LIGHTWEIGHT_API
size_t LastInsertId(std::string_view tableName);
277 [[nodiscard]] LIGHTWEIGHT_API
size_t NumRowsAffected() const;
278 [[nodiscard]] LIGHTWEIGHT_API
size_t NumColumnsAffected() const;
279 [[nodiscard]] LIGHTWEIGHT_API
bool FetchRow();
280 [[nodiscard]] LIGHTWEIGHT_API std::expected<
bool,
SqlErrorInfo> TryFetchRow(
281 std::source_location location = std::source_location::current()) noexcept;
282 void CloseCursor() noexcept;
287 template <SqlOutputColumnBinder... Args>
288 void BindOutputColumns(Args*... args);
294 template <typename... Records>
295 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
296 void BindOutputColumnsToRecord(Records*... records);
302 template <SqlOutputColumnBinder T>
303 void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg);
305 template <SqlGetColumnNativeType T>
306 [[nodiscard]]
bool GetColumn(SQLUSMALLINT column, T* result) const;
308 template <SqlGetColumnNativeType T>
309 [[nodiscard]] T GetColumn(SQLUSMALLINT column) const;
313 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
314 [[nodiscard]]
SqlResultCursor ExecuteBatchNativeRowWise(Rows const& rows, ColumnAccessors const&... accessors);
318 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
319 [[nodiscard]]
SqlResultCursor ExecuteBatchSoftRowMajor(Rows const& rows, ColumnAccessors const&... accessors);
336 template <typename Record, typename... ColumnAccessors>
337 void FetchAllRowWise(std::vector<Record>& out, std::
size_t arrayDepth, ColumnAccessors const&... accessors);
342 template <typename ValueType>
343 [[nodiscard]] SQLLEN* BindRowWiseOutputColumn(SQLUSMALLINT column,
345 std::
size_t rowStride,
352 template <typename Value>
353 void BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators);
357 template <typename ValueType>
358 static
void FinalizeRowWiseOutputColumn(
void* base0,
359 std::
size_t rowStride,
360 std::
size_t rowCount,
361 SQLLEN const* indicators) noexcept;
363 template <SqlGetColumnNativeType T>
364 [[nodiscard]] std::optional<T> GetNullableColumn(SQLUSMALLINT column) const;
366 template <SqlGetColumnNativeType T>
367 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const;
369 LIGHTWEIGHT_API
void RequireSuccess(SQLRETURN error,
370 std::source_location sourceLocation = std::source_location::current()) const;
371 LIGHTWEIGHT_API
void PlanPostExecuteCallback(std::function<
void()>&& cb) override;
372 LIGHTWEIGHT_API
void PlanPostProcessOutputColumn(std::function<
void()>&& cb) override;
373 [[nodiscard]] LIGHTWEIGHT_API SqlServerType ServerType() const noexcept override;
374 [[nodiscard]] LIGHTWEIGHT_API std::
string const& DriverName() const noexcept override;
375 LIGHTWEIGHT_API
void ProcessPostExecuteCallbacks();
377 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicator() override;
378 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicators(
size_t rowCount) override;
379 LIGHTWEIGHT_API std::
byte* ProvideBatchStagingBuffer(std::
size_t byteCount) override;
380 LIGHTWEIGHT_API
void ClearBatchIndicators();
383 LIGHTWEIGHT_API
void ResetParameterArrayBinding() noexcept;
386 LIGHTWEIGHT_API
void RequireExecuteSucceededOrNoData(
387 SQLRETURN result, std::source_location sourceLocation = std::source_location::current()) const;
390 LIGHTWEIGHT_API
void RequireSuccessfulBatchExecute(
392 SQLULEN processedCount,
393 SQLULEN expectedCount,
394 std::source_location sourceLocation = std::source_location::current()) const;
395 LIGHTWEIGHT_API
void RequireIndicators();
396 LIGHTWEIGHT_API SQLLEN* GetIndicatorForColumn(SQLUSMALLINT column) noexcept;
405 [[nodiscard]] std::
size_t EffectivePrefetchDepth() const noexcept;
407 void ArmPrefetchOnFirstFetch() noexcept;
410 [[nodiscard]] std::expected<
bool,
SqlErrorInfo> FetchRowPrefetched() noexcept;
412 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrefetchActive() const noexcept;
414 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor const& PrefetchCursorRef() const noexcept;
416 [[nodiscard]] LIGHTWEIGHT_API std::
size_t PrefetchRowInBlock() const noexcept;
419 [[nodiscard]] LIGHTWEIGHT_API
bool ShouldRecordPrefetchBinding() const noexcept;
421 LIGHTWEIGHT_API
void ResetPrefetchBindings() noexcept;
424 LIGHTWEIGHT_API
void MarkPrefetchBindingUnsupported() noexcept;
433 LIGHTWEIGHT_API
void RecordPrefetchColumn(SQLUSMALLINT column,
434 std::function<
void()> scatter,
435 std::function<
void()> deferredBind);
437 LIGHTWEIGHT_API
void ResetPrefetchState() noexcept;
441 SQLUSMALLINT column) const;
443 template <typename T>
444 [[nodiscard]] T ConvertCell(
RowArrayCursor const& cursor, std::
size_t row, SQLUSMALLINT column) const;
449 LIGHTWEIGHT_API
void RequirePrefetchColumnInRange(
RowArrayCursor const& cursor, SQLUSMALLINT column) const;
453 template <SqlOutputColumnBinder T>
454 void RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg);
458 std::unique_ptr<Data,
void (*)(Data*)> m_data;
461 std::string m_preparedQuery;
462 std::optional<SQLSMALLINT> m_numColumns;
463 SQLSMALLINT m_expectedParameterCount {};
476 SqlResultCursor() =
delete;
477 SqlResultCursor(SqlResultCursor
const&) =
delete;
478 SqlResultCursor& operator=(SqlResultCursor
const&) =
delete;
482 m_stmt { other.m_stmt }
484 other.m_stmt =
nullptr;
492 m_stmt = other.m_stmt;
493 other.m_stmt =
nullptr;
502 m_stmt->CloseCursor();
510 return m_stmt->NumRowsAffected();
516 return m_stmt->NumColumnsAffected();
522 template <SqlOutputColumnBinder... Args>
525 m_stmt->BindOutputColumns(args...);
529 template <SqlOutputColumnBinder T>
532 m_stmt->BindOutputColumn(columnIndex, arg);
536 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool FetchRow()
538 return m_stmt->FetchRow();
542 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::expected<bool, SqlErrorInfo>
TryFetchRow(
543 std::source_location location = std::source_location::current()) noexcept
545 return m_stmt->TryFetchRow(location);
549 template <
typename... Records>
550 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
553 m_stmt->BindOutputColumnsToRecord(records...);
562 template <
typename Record,
typename... ColumnAccessors>
564 std::size_t arrayDepth,
565 ColumnAccessors
const&... accessors)
567 m_stmt->FetchAllRowWise(out, arrayDepth, accessors...);
573 template <SqlGetColumnNativeType T>
574 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool GetColumn(SQLUSMALLINT column, T* result)
const
576 return m_stmt->GetColumn<T>(column, result);
580 template <SqlGetColumnNativeType T>
581 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(SQLUSMALLINT column)
const
583 return m_stmt->GetColumn<T>(column);
589 template <SqlGetColumnNativeType T>
590 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(SQLUSMALLINT column)
const
592 return m_stmt->GetNullableColumn<T>(column);
598 template <SqlGetColumnNativeType T>
599 [[nodiscard]] T
GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
601 return m_stmt->GetColumnOr(column, std::forward<T>(defaultValue));
619 using std::runtime_error::runtime_error;
653 static constexpr std::size_t MaxCharColumnBytes = 8192;
659 static constexpr std::size_t MemoryBudgetBytes = 4 * 1024 * 1024;
663 static constexpr std::size_t MinArrayDepth = 16;
685 [[nodiscard]] LIGHTWEIGHT_API std::
size_t FetchArray();
688 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ColumnCount() const noexcept;
693 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ArrayDepth() const noexcept;
699 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::int64_t> GetI64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
705 [[nodiscard]] LIGHTWEIGHT_API std::optional<
double> GetF64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
718 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::
string> GetString(std::
size_t rowInBatch, SQLUSMALLINT column) const;
724 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDate> GetDate(std::
size_t rowInBatch, SQLUSMALLINT column) const;
731 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDateTime> GetTimestamp(std::
size_t rowInBatch, SQLUSMALLINT column) const;
738 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlGuid> GetGuid(std::
size_t rowInBatch, SQLUSMALLINT column) const;
764 [[nodiscard]] LIGHTWEIGHT_API SQLSMALLINT
ColumnSqlType(SQLUSMALLINT column)
const;
770 [[nodiscard]] LIGHTWEIGHT_API
bool IsCellNull(std::size_t rowInBatch, SQLUSMALLINT column)
const;
777 SQLSMALLINT sqlType {};
778 std::size_t elementWidth {};
779 std::vector<char> buffer;
780 std::vector<SQLLEN> indicators;
783 void ResetStatementState() noexcept;
788 [[nodiscard]]
char const* CheckedCell(std::
size_t rowInBatch,
791 char const* accessorName) const;
793 SqlStatement* m_stmt;
794 std::
size_t m_arrayDepth;
795 std::
size_t m_lastFetched = 0;
796 std::vector<BoundColumn> m_columns;
797 SQLULEN m_rowsFetched = 0;
798 std::vector<SQLUSMALLINT> m_rowStatus;
801struct [[nodiscard]] SqlSentinelIterator
805class [[nodiscard]] SqlVariantRowIterator
808 explicit SqlVariantRowIterator(SqlSentinelIterator )
noexcept:
813 explicit SqlVariantRowIterator(SqlResultCursor& cursor)
noexcept:
814 _numResultColumns {
static_cast<SQLUSMALLINT
>(cursor.NumColumnsAffected()) },
817 _row.reserve(_numResultColumns);
821 SqlVariantRow& operator*() noexcept
826 SqlVariantRow
const& operator*() const noexcept
831 SqlVariantRowIterator& operator++() noexcept
833 _end = !_cursor->FetchRow();
837 for (
auto const i: std::views::iota(SQLUSMALLINT(1), SQLUSMALLINT(_numResultColumns + 1)))
838 _row.emplace_back(_cursor->GetColumn<SqlVariant>(i));
843 bool operator!=(SqlSentinelIterator )
const noexcept
848 bool operator!=(SqlVariantRowIterator
const& )
const noexcept
855 SQLUSMALLINT _numResultColumns = 0;
856 SqlResultCursor* _cursor;
860class [[nodiscard]] SqlVariantRowCursor
863 explicit SqlVariantRowCursor(SqlResultCursor&& cursor):
864 _resultCursor { std::move(cursor) }
868 SqlVariantRowIterator begin() noexcept
870 return SqlVariantRowIterator { _resultCursor };
873 static SqlSentinelIterator end() noexcept
875 return SqlSentinelIterator {};
879 SqlResultCursor _resultCursor;
907 _connection { &conn }
914 using difference_type = bool;
915 using value_type = T;
917 iterator& operator++()
921 _is_end = !_cursor->FetchRow();
928 LIGHTWEIGHT_FORCE_INLINE value_type operator*() noexcept
936 SQLUSMALLINT columnIndex = 0;
938 if constexpr (RecordColumnMember<FieldType>)
941 if constexpr (FieldWithStorage<FieldType>)
942 value = _cursor->GetColumn<
typename FieldType::ValueType>(columnIndex);
944 value = _cursor->GetColumn<FieldType>(columnIndex);
951 LIGHTWEIGHT_FORCE_INLINE
constexpr bool operator!=(iterator
const& other)
const noexcept
953 return _is_end != other._is_end;
956 constexpr iterator(std::default_sentinel_t )
noexcept:
958 _cursor { std::nullopt }
962 explicit iterator(SqlConnection& conn):
963 _stmt { std::make_unique<SqlStatement>(conn) },
964 _cursor { std::nullopt }
968 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
973 void SetCursor(SqlResultCursor cursor)
noexcept
975 _cursor.emplace(std::move(cursor));
979 bool _is_end =
false;
980 std::unique_ptr<SqlStatement> _stmt;
981 std::optional<SqlResultCursor> _cursor;
987 auto it = iterator { *_connection };
988 auto& stmt = it.Statement();
989 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().All());
990 it.SetCursor(stmt.Execute());
998 return iterator { std::default_sentinel };
1006inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsAlive() const noexcept
1008 return m_connection && m_connection->
IsAlive() && m_hStmt !=
nullptr;
1011inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsPrepared() const noexcept
1013 return !m_preparedQuery.empty();
1016inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection& SqlStatement::Connection() noexcept
1018 return *m_connection;
1021inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection const& SqlStatement::Connection() const noexcept
1023 return *m_connection;
1026inline LIGHTWEIGHT_FORCE_INLINE
SqlErrorInfo SqlStatement::LastError()
const
1028 return SqlErrorInfo::FromStatementHandle(m_hStmt);
1031inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
1036inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::Prepare(
SqlQueryObject auto const& queryObject) &
1038 Prepare(queryObject.ToSql());
1043 return Prepare(queryObject.ToSql());
1046inline LIGHTWEIGHT_FORCE_INLINE std::string
const& SqlStatement::PreparedQuery() const noexcept
1048 return m_preparedQuery;
1052template <SqlOutputColumnBinder... Args>
1053inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumns(Args*... args)
1055 if (ShouldRecordPrefetchBinding())
1060 ResetPrefetchBindings();
1062 ((++i, RecordPrefetchOutputColumn<Args>(i, args)), ...);
1066 RequireIndicators();
1069 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *
this))), ...);
1072template <
typename... Records>
1073 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
1074void SqlStatement::BindOutputColumnsToRecord(Records*... records)
1076 if (ShouldRecordPrefetchBinding())
1078 ResetPrefetchBindings();
1081 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1086 this->RecordPrefetchOutputColumn<FieldType>(i, &value);
1093 RequireIndicators();
1097 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1099 if constexpr (RecordColumnMember<FieldType>)
1102 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
1103 m_hStmt, i, &value, GetIndicatorForColumn(i), *
this));
1110template <SqlOutputColumnBinder T>
1111inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
1115 if (ShouldRecordPrefetchBinding())
1117 RecordPrefetchOutputColumn<T>(columnIndex, arg);
1121 RequireIndicators();
1123 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *
this));
1127template <SqlInputParameterBinder Arg>
1128inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg
const& arg)
1131 m_expectedParameterCount = (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)();
1132 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt,
static_cast<SQLUSMALLINT
>(columnIndex), arg, *
this));
1136template <SqlInputParameterBinder Arg,
typename ColumnName>
1137inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
1139 ColumnName&& columnNameHint)
1141 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
1142 BindInputParameter(columnIndex, arg);
1145template <SqlInputParameterBinder... Args>
1152 ZoneScopedN(
"SqlStatement::Execute");
1153 ZoneTextObject(m_preparedQuery);
1154 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1156 if (!(m_expectedParameterCount == (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)()
1157 &&
sizeof...(args) == 0)
1158 && !(m_expectedParameterCount ==
sizeof...(args)))
1159 throw std::invalid_argument {
"Invalid argument count" };
1163 SqlLogger::GetLogger().OnBindInputParameter({}, args),
1164 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *
this))),
1167 auto const result = SQLExecute(m_hStmt);
1169 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
1170 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
1172 ProcessPostExecuteCallbacks();
1177template <
typename T>
1178concept SqlNativeContiguousValueConcept =
1179 std::same_as<T, bool>
1180 || std::same_as<T, char>
1181 || std::same_as<T, unsigned char>
1182 || std::same_as<T, wchar_t>
1183 || std::same_as<T, std::int16_t>
1184 || std::same_as<T, std::uint16_t>
1185 || std::same_as<T, std::int32_t>
1186 || std::same_as<T, std::uint32_t>
1187 || std::same_as<T, std::int64_t>
1188 || std::same_as<T, std::uint64_t>
1189 || std::same_as<T, float>
1190 || std::same_as<T, double>
1191 || std::same_as<T, SqlDate>
1192 || std::same_as<T, SqlTime>
1193 || std::same_as<T, SqlDateTime>
1194 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
1196template <
typename FirstColumnBatch,
typename... MoreColumnBatches>
1197concept SqlNativeBatchable =
1198 std::ranges::contiguous_range<FirstColumnBatch>
1199 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
1200 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
1201 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
1208template <
typename V>
1214template <
typename V>
1220template <
typename V>
1233template <
typename V>
1242template <
typename V>
1244 requires(SQLHSTMT stmt, SQLUSMALLINT column, V
const* elem0, std::size_t n, SqlDataBinderCallback& cb) {
1245 { SqlDataBinder<V>::BatchRowWiseInputParameter(stmt, column, elem0, n, n, cb) } -> std::same_as<SQLRETURN>;
1248template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
1250 MoreColumnBatches
const&... moreColumnBatches)
1252 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
1253 "Must be a supported native contiguous element type.");
1255 ZoneScopedN(
"SqlStatement::ExecuteBatchNative");
1256 ZoneTextObject(m_preparedQuery);
1258 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1259 throw std::invalid_argument {
"Invalid number of columns" };
1261 auto const rowCount = std::ranges::size(firstColumnBatch);
1262 ZoneValue(rowCount);
1263 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1264 throw std::invalid_argument {
"Uneven number of rows" };
1266 size_t rowStart = 0;
1270 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1271 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
1272 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
1273 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1274 ClearBatchIndicators();
1275 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(firstColumnBatch))>>::
1276 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *
this));
1277 SQLUSMALLINT column = 1;
1278 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(moreColumnBatches))>>::
1279 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *
this)),
1281 RequireSuccess(SQLExecute(m_hStmt));
1282 ProcessPostExecuteCallbacks();
1288template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1289inline LIGHTWEIGHT_FORCE_INLINE
SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch
const& firstColumnBatch,
1290 MoreColumnBatches
const&... moreColumnBatches)
1294 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
1295 return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
1297 return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
1300template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1302 MoreColumnBatches
const&... moreColumnBatches)
1304 ZoneScopedN(
"SqlStatement::ExecuteBatchSoft");
1305 ZoneTextObject(m_preparedQuery);
1307 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1308 throw std::invalid_argument {
"Invalid number of columns" };
1310 auto const rowCount = std::ranges::size(firstColumnBatch);
1311 ZoneValue(rowCount);
1312 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1313 throw std::invalid_argument {
"Uneven number of rows" };
1315 for (
auto const rowIndex: std::views::iota(
size_t { 0 }, rowCount))
1318 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues
const&... columnsInRow) {
1319 SQLUSMALLINT column = 0;
1320 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
1321 RequireSuccess(SQLExecute(m_hStmt));
1322 ProcessPostExecuteCallbacks();
1325 std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch),
static_cast<std::ptrdiff_t
>(rowIndex))),
1327 *std::ranges::next(std::ranges::begin(moreColumnBatches),
static_cast<std::ptrdiff_t
>(rowIndex)))...));
1332template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1333 requires(
sizeof...(ColumnAccessors) >= 1
1334 && (std::invocable<ColumnAccessors
const&, std::ranges::range_value_t<Rows>
const&> && ...))
1335SqlResultCursor SqlStatement::ExecuteBatch(Rows
const& rows, ColumnAccessors
const&... accessors)
1337 ZoneScopedN(
"SqlStatement::ExecuteBatch(row-major)");
1338 ZoneTextObject(m_preparedQuery);
1340 using RowElem = std::ranges::range_value_t<Rows>;
1342 auto const rowCount = std::ranges::size(rows);
1346 if (m_expectedParameterCount !=
static_cast<SQLSMALLINT
>(
sizeof...(accessors)))
1347 throw std::invalid_argument {
"Invalid number of columns" };
1353 constexpr bool allColumnsRowBindable =
1355 constexpr bool allAccessorsReturnReference =
1356 (std::is_reference_v<std::invoke_result_t<ColumnAccessors const&, RowElem const&>> && ...);
1357 constexpr bool anyStridedIndicatorColumn =
1359 constexpr bool indicatorAlignmentSatisfied = (
sizeof(RowElem) %
alignof(SQLLEN)) == 0;
1361 if constexpr (allColumnsRowBindable && allAccessorsReturnReference
1362 && (!anyStridedIndicatorColumn || indicatorAlignmentSatisfied))
1364 auto const* rowData = std::ranges::data(rows);
1368 auto const accessorStrideMatchesRow = [&](
auto const& accessor)
noexcept ->
bool {
1369 auto const* first =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[0])));
1370 auto const* second =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[1])));
1371 return static_cast<std::size_t
>(second - first) ==
sizeof(RowElem);
1373 bool const rowStrideOk = rowCount < 2 || (accessorStrideMatchesRow(accessors) && ...);
1375 if (m_connection->SupportsNativeRowBatch() && rowStrideOk)
1376 return ExecuteBatchNativeRowWise(rows, accessors...);
1379 return ExecuteBatchSoftRowMajor(rows, accessors...);
1382template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1383SqlResultCursor SqlStatement::ExecuteBatchNativeRowWise(Rows
const& rows, ColumnAccessors
const&... accessors)
1385 ZoneScopedN(
"SqlStatement::ExecuteBatchNativeRowWise");
1386 ZoneTextObject(m_preparedQuery);
1388 using RowElem = std::ranges::range_value_t<Rows>;
1389 auto const rowCount = std::ranges::size(rows);
1390 ZoneValue(rowCount);
1391 auto const* rowData = std::ranges::data(rows);
1395 SQLULEN processedCount = rowCount;
1401 auto const restoreParameterBinding = detail::Finally([
this] {
1402 ResetParameterArrayBinding();
1403 ClearBatchIndicators();
1409 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1411 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)
sizeof(RowElem), 0));
1412 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
nullptr, 0));
1413 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1414 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &processedCount, 0));
1417 SQLUSMALLINT column = 0;
1418 auto const bindColumn = [&](
auto const& accessor) {
1420 using ValueType = std::remove_cvref_t<
decltype(accessor(rowData[0]))>;
1423 if constexpr (SqlHasRowWiseBatchBinder<ValueType>)
1424 RequireSuccess(SqlDataBinder<ValueType>::BatchRowWiseInputParameter(
1425 m_hStmt, column, std::addressof(accessor(rowData[0])),
sizeof(RowElem), rowCount, *
this));
1427 RequireSuccess(SqlDataBinder<ValueType>::InputParameter(m_hStmt, column, accessor(rowData[0]), *
this));
1429 (bindColumn(accessors), ...);
1431 SqlLogger::GetLogger().OnExecuteBatch();
1434 auto const executeResult = SQLExecute(m_hStmt);
1435 RequireSuccessfulBatchExecute(executeResult, processedCount,
static_cast<SQLULEN
>(rowCount));
1436 ProcessPostExecuteCallbacks();
1438 return SqlResultCursor { *
this };
1441template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1442SqlResultCursor SqlStatement::ExecuteBatchSoftRowMajor(Rows
const& rows, ColumnAccessors
const&... accessors)
1444 ZoneScopedN(
"SqlStatement::ExecuteBatchSoftRowMajor");
1445 ZoneTextObject(m_preparedQuery);
1447 auto const* rowData = std::ranges::data(rows);
1448 auto const rowCount = std::ranges::size(rows);
1449 ZoneValue(rowCount);
1451 for (
auto const rowIndex: std::views::iota(std::size_t { 0 }, rowCount))
1453 auto const& row = rowData[rowIndex];
1454 SQLUSMALLINT column = 0;
1456 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(accessors(row))>>::InputParameter(
1457 m_hStmt, column, accessors(row), *
this))),
1459 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1460 RequireExecuteSucceededOrNoData(SQLExecute(m_hStmt));
1461 ProcessPostExecuteCallbacks();
1464 return SqlResultCursor { *
this };
1467template <
typename Value>
1468void SqlStatement::BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators)
1470 if constexpr (IsSqlFixedString<Value>)
1477 RequireSuccess(SQLBindCol(m_hStmt,
1480 (SQLPOINTER) SqlBasicStringOperations<Value>::Data(
static_cast<Value*
>(base0)),
1481 static_cast<SQLLEN
>(Value::Capacity) + 1,
1488 RequireSuccess(SqlDataBinder<Value>::OutputColumn(m_hStmt, column,
static_cast<Value*
>(base0), indicators, *
this));
1492template <
typename ValueType>
1493SQLLEN* SqlStatement::BindRowWiseOutputColumn(SQLUSMALLINT column,
void* base0, std::size_t rowStride, std::size_t depth)
1499 auto*
const indicatorBytes = ProvideBatchStagingBuffer(((depth - 1) * rowStride) +
sizeof(SQLLEN));
1500 auto*
const indicators =
reinterpret_cast<SQLLEN*
>(indicatorBytes);
1502 if constexpr (SqlIsStdOptional<ValueType>)
1504 using Inner = ValueType::value_type;
1505 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1508 for (
auto const i: std::views::iota(std::size_t { 0 }, depth))
1509 reinterpret_cast<ValueType*>(optBytes + (i * rowStride))->emplace();
1512 auto*
const contained0 =
reinterpret_cast<Inner*
>(optBytes + detail::OptionalValueOffset<Inner>());
1513 BindRowWiseValue<Inner>(column, contained0, indicators);
1517 BindRowWiseValue<ValueType>(column, base0, indicators);
1522template <
typename ValueType>
1523void SqlStatement::FinalizeRowWiseOutputColumn(
void* base0,
1524 std::size_t rowStride,
1525 std::size_t rowCount,
1526 SQLLEN
const* indicators)
noexcept
1528 auto const indicatorAt = [&](std::size_t i)
noexcept {
1529 return *
reinterpret_cast<SQLLEN const*
>(
reinterpret_cast<std::byte const*
>(indicators) + (i * rowStride));
1532 if constexpr (SqlIsStdOptional<ValueType>)
1534 using Inner = ValueType::value_type;
1535 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1536 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1538 auto*
const optional =
reinterpret_cast<ValueType*
>(optBytes + (i * rowStride));
1539 if (indicatorAt(i) == SQL_NULL_DATA)
1541 else if constexpr (IsSqlFixedString<Inner>)
1547 if (optional->has_value())
1548 SqlBasicStringOperations<Inner>::PostProcessOutputColumn(std::addressof(**optional), indicatorAt(i));
1553 else if constexpr (IsSqlFixedString<ValueType>)
1555 auto*
const base =
static_cast<std::byte*
>(base0);
1556 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1557 SqlBasicStringOperations<ValueType>::PostProcessOutputColumn(
1558 reinterpret_cast<ValueType*>(base + (i * rowStride)), indicatorAt(i));
1564template <
typename Record,
typename... ColumnAccessors>
1565void SqlStatement::FetchAllRowWise(std::vector<Record>& out, std::size_t arrayDepth, ColumnAccessors
const&... accessors)
1567 ZoneScopedN(
"SqlStatement::FetchAllRowWise");
1568 ZoneTextObject(m_preparedQuery);
1570 static_assert(
sizeof...(ColumnAccessors) >= 1,
"FetchAllRowWise requires at least one column accessor");
1571 constexpr std::size_t columnCount =
sizeof...(ColumnAccessors);
1578 auto const perRow =
sizeof(Record) * (1 + columnCount);
1579 auto const budgetDepth = RowArrayCursor::MemoryBudgetBytes / std::max<std::size_t>(perRow, 1);
1580 auto const minDepth = std::min(RowArrayCursor::MinArrayDepth, arrayDepth);
1581 arrayDepth = std::clamp(budgetDepth, minDepth, arrayDepth);
1584 std::vector<SQLUSMALLINT> rowStatus(arrayDepth);
1585 SQLULEN rowsFetched = 0;
1590 auto const restoreFetchState = detail::Finally([
this] {
1591 SQLFreeStmt(m_hStmt, SQL_UNBIND);
1594 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, 0);
1595 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
1596 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR,
nullptr, 0);
1597 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR,
nullptr, 0);
1599 ClearBatchIndicators();
1604 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)
sizeof(Record), 0));
1606 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arrayDepth, 0));
1607 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, rowStatus.data(), 0));
1608 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowsFetched, 0));
1613 std::size_t
const base = out.size();
1614 out.resize(base + arrayDepth);
1615 Record*
const row0 = out.data() + base;
1619 ClearBatchIndicators();
1620 std::array<SQLLEN*, columnCount> indicators {};
1621 SQLUSMALLINT column = 0;
1622 std::size_t bindIndex = 0;
1623 ((indicators[bindIndex++] = BindRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1624 ++column, std::addressof(accessors(*row0)),
sizeof(Record), arrayDepth)),
1628 auto const fetchResult = SQLFetchScroll(m_hStmt, SQL_FETCH_NEXT, 0);
1629 if (fetchResult == SQL_NO_DATA)
1636 if (!SQL_SUCCEEDED(fetchResult))
1637 RequireSuccess(fetchResult);
1639 auto const fetched =
static_cast<std::size_t
>(rowsFetched);
1640 SqlLogger::GetLogger().OnFetchRow();
1642 std::size_t finalizeIndex = 0;
1643 (FinalizeRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1644 std::addressof(accessors(*row0)),
sizeof(Record), fetched, indicators[finalizeIndex++]),
1647 out.resize(base + fetched);
1648 if (fetched < arrayDepth)
1652 SqlLogger::GetLogger().OnFetchEnd();
1655template <SqlGetColumnNativeType T>
1656inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result)
const
1658 if (IsPrefetchActive())
1660 auto const& cursor = PrefetchCursorRef();
1661 auto const row = PrefetchRowInBlock();
1662 RequirePrefetchColumnInRange(cursor, column);
1663 if (cursor.IsCellNull(row, column))
1665 *result = ConvertCell<T>(cursor, row, column);
1668 SQLLEN indicator {};
1669 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *
this));
1670 return indicator != SQL_NULL_DATA;
1676 template <
typename T>
1677 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
1680 template <
typename T>
1681 struct IsSqlFixedStringSpec: std::false_type
1684 template <std::
size_t N,
typename Char, SqlFixedStringMode Mode>
1685 struct IsSqlFixedStringSpec<SqlFixedString<N, Char, Mode>>: std::true_type
1688 template <
typename T>
1689 concept SqlFixedStringCell = IsSqlFixedStringSpec<std::remove_cvref_t<T>>::value;
1692 template <
typename T>
1693 concept PlainStringCell =
1694 std::same_as<T, std::string> || std::same_as<T, std::u8string> || std::same_as<T, std::u16string>
1695 || std::same_as<T, std::u32string> || std::same_as<T, std::wstring>;
1698 template <
typename T>
1699 struct IsSqlNumericSpec: std::false_type
1702 template <std::
size_t Precision, std::
size_t Scale>
1703 struct IsSqlNumericSpec<SqlNumeric<Precision, Scale>>: std::true_type
1706 template <
typename T>
1707 concept SqlNumericCell = IsSqlNumericSpec<std::remove_cvref_t<T>>::value;
1710 [[nodiscard]]
inline std::u8string_view AsU8View(std::string
const& utf8)
noexcept
1712 return std::u8string_view {
reinterpret_cast<char8_t const*
>(utf8.data()), utf8.size() };
1724 template <SqlFixedStringMode Mode>
1725 inline void TrimFixedStringBytes(std::string& bytes)
noexcept
1727 auto const isTrailingTrimmable = [](
char c)
noexcept {
1730 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
1731 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
1735 while (!bytes.empty() && isTrailingTrimmable(bytes.back()))
1745 template <
typename Char>
1746 [[nodiscard]]
inline std::basic_string<Char> DecodeUtf8To(std::string
const& utf8)
1748 if constexpr (std::same_as<Char, char>)
1750 else if constexpr (std::same_as<Char, char8_t>)
1751 return std::u8string { AsU8View(utf8) };
1752 else if constexpr (std::same_as<Char, char16_t>)
1753 return ToUtf16(AsU8View(utf8));
1754 else if constexpr (std::same_as<Char, char32_t>)
1755 return ToUtf32<std::u32string>(AsU8View(utf8));
1763 template <
typename T>
1764 concept StringLikeCell = PlainStringCell<T> || SqlStringInterface<T>;
1769 template <
typename T>
1770 concept PrefetchConvertibleScalar =
1771 std::same_as<T, SqlVariant> || std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>
1772 || StringLikeCell<T> || std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>;
1774 template <
typename T>
1775 struct PrefetchConvertibleOptional: std::false_type
1778 template <
typename U>
1779 struct PrefetchConvertibleOptional<std::optional<U>>: std::bool_constant<PrefetchConvertibleScalar<U>>
1784 template <
typename T>
1785 concept PrefetchConvertible = PrefetchConvertibleScalar<T> || PrefetchConvertibleOptional<T>::value;
1790 template <
typename T>
1791 [[nodiscard]]
inline T ReadTemporalGuidCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1793 using BoundType = RowArrayCursor::BoundType;
1794 auto const boundType = cursor.ColumnBoundType(column);
1795 if constexpr (std::same_as<T, SqlDate>)
1796 return boundType == BoundType::Date ? cursor.GetDate(row, column).value_or(SqlDate {}) : SqlDate {};
1797 else if constexpr (std::same_as<T, SqlDateTime>)
1798 return boundType == BoundType::Timestamp ? cursor.GetTimestamp(row, column).value_or(SqlDateTime {})
1802 if (boundType == BoundType::Guid)
1803 return cursor.GetGuid(row, column).value_or(SqlGuid {});
1804 if (boundType == BoundType::Char || boundType == BoundType::WChar)
1805 return SqlGuid::TryParse(cursor.GetString(row, column).value_or(std::string {})).value_or(SqlGuid {});
1812 template <
typename T>
1813 [[nodiscard]]
inline T ReadNumericCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1815 using BoundType = RowArrayCursor::BoundType;
1816 switch (cursor.ColumnBoundType(column))
1818 case BoundType::Double:
1819 return T { cursor.GetF64(row, column).value_or(0.0) };
1820 case BoundType::Int64:
1821 return T {
static_cast<double>(cursor.GetI64(row, column).value_or(0)) };
1833 [[nodiscard]]
inline std::string RenderCellAsUtf8(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1835 switch (cursor.ColumnBoundType(column))
1837 case RowArrayCursor::BoundType::Char:
1838 case RowArrayCursor::BoundType::WChar:
1839 return cursor.GetString(row, column).value_or(std::string {});
1840 case RowArrayCursor::BoundType::Int64:
1841 return std::format(
"{}", cursor.GetI64(row, column).value_or(0));
1842 case RowArrayCursor::BoundType::Double:
1843 return std::format(
"{}", cursor.GetF64(row, column).value_or(0.0));
1844 case RowArrayCursor::BoundType::Date:
1845 return std::format(
"{}", cursor.GetDate(row, column).value_or(SqlDate {}));
1846 case RowArrayCursor::BoundType::Timestamp:
1847 return std::format(
"{}", cursor.GetTimestamp(row, column).value_or(SqlDateTime {}));
1848 case RowArrayCursor::BoundType::Guid:
1849 return std::format(
"{}", cursor.GetGuid(row, column).value_or(SqlGuid {}));
1851 return std::string {};
1859 template <
typename T>
1860 [[nodiscard]]
inline T ReadStringLikeCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1862 auto utf8 = RenderCellAsUtf8(cursor, row, column);
1863 if constexpr (SqlFixedStringCell<T>)
1864 TrimFixedStringBytes<T::PostRetrieveOperation>(utf8);
1865 return T { DecodeUtf8To<typename T::value_type>(utf8) };
1871 template <
typename T>
1872 [[nodiscard]]
inline T ReadArithmeticCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1874 using BoundType = RowArrayCursor::BoundType;
1875 switch (cursor.ColumnBoundType(column))
1877 case BoundType::Int64:
1878 return static_cast<T
>(cursor.GetI64(row, column).value_or(0));
1879 case BoundType::Double:
1880 return static_cast<T
>(cursor.GetF64(row, column).value_or(0.0));
1888template <
typename T>
1889inline T SqlStatement::ConvertCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
const
1894 if constexpr (std::same_as<T, SqlVariant>)
1895 return MakePrefetchVariantCell(cursor, row, column);
1896 else if constexpr (IsSpecializationOf<std::optional, T>)
1898 if (cursor.IsCellNull(row, column))
1899 return std::nullopt;
1900 return T { ConvertCell<typename T::value_type>(cursor, row, column) };
1902 else if constexpr (std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>)
1903 return detail::ReadTemporalGuidCell<T>(cursor, row, column);
1904 else if constexpr (detail::SqlNumericCell<T>)
1905 return detail::ReadNumericCell<T>(cursor, row, column);
1906 else if constexpr (detail::StringLikeCell<T>)
1907 return detail::ReadStringLikeCell<T>(cursor, row, column);
1908 else if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>)
1909 return detail::ReadArithmeticCell<T>(cursor, row, column);
1917template <SqlOutputColumnBinder T>
1918inline void SqlStatement::RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg)
1920 auto deferredBind = [
this, column, arg] {
1921 RequireIndicators();
1922 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, column, arg, GetIndicatorForColumn(column), *
this));
1924 if constexpr (detail::PrefetchConvertible<T>)
1926 RecordPrefetchColumn(
1928 [
this, column, arg] { *arg = ConvertCell<T>(PrefetchCursorRef(), PrefetchRowInBlock(), column); },
1929 std::move(deferredBind));
1935 RecordPrefetchColumn(column, {}, std::move(deferredBind));
1936 MarkPrefetchBindingUnsupported();
1940template <SqlGetColumnNativeType T>
1941inline T SqlStatement::GetColumn(SQLUSMALLINT column)
const
1943 if (IsPrefetchActive())
1945 auto const& cursor = PrefetchCursorRef();
1946 auto const row = PrefetchRowInBlock();
1947 RequirePrefetchColumnInRange(cursor, column);
1948 if constexpr (!detail::SqlNullableType<T>)
1949 if (cursor.IsCellNull(row, column))
1950 throw std::runtime_error {
"Column value is NULL" };
1951 return ConvertCell<T>(cursor, row, column);
1954 SQLLEN indicator {};
1959 ZoneScopedN(
"SqlStatement::ColumnGetData");
1960 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
1962 if constexpr (!detail::SqlNullableType<T>)
1963 if (indicator == SQL_NULL_DATA)
1964 throw std::runtime_error {
"Column value is NULL" };
1968template <SqlGetColumnNativeType T>
1969inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column)
const
1971 if (IsPrefetchActive())
1973 auto const& cursor = PrefetchCursorRef();
1974 auto const row = PrefetchRowInBlock();
1975 RequirePrefetchColumnInRange(cursor, column);
1976 if (cursor.IsCellNull(row, column))
1977 return std::nullopt;
1978 return ConvertCell<T>(cursor, row, column);
1981 SQLLEN indicator {};
1983 ZoneScopedN(
"SqlStatement::ColumnGetData");
1984 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
1986 if (indicator == SQL_NULL_DATA)
1987 return std::nullopt;
1988 return { std::move(result) };
1991template <SqlGetColumnNativeType T>
1992T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
1994 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
1998 std::source_location location)
2000 return ExecuteDirect(query.ToSql(), location);
2003template <
typename Callable>
2004 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
2005void SqlStatement::MigrateDirect(Callable
const& callable, std::source_location location)
2007 ZoneScopedN(
"SqlStatement::MigrateDirect");
2009 callable(migration);
2010 auto const queries = migration.GetPlan().ToSql();
2011 ZoneValue(queries.size());
2017 auto const isCommentOnlyGuardScript = [](std::string_view script) {
2018 constexpr std::string_view marker =
"-- LIGHTWEIGHT_SQLITE_GUARD:";
2019 if (!script.starts_with(marker))
2021 auto const newline = script.find(
'\n');
2022 if (newline == std::string_view::npos)
2024 auto const body = script.substr(newline + 1);
2025 auto const bodyStart = body.find_first_not_of(
" \t\r\n");
2026 return bodyStart == std::string_view::npos || body.substr(bodyStart).starts_with(
"--");
2029 for (
auto const& query: queries)
2031 if (isCommentOnlyGuardScript(query))
2032 throw std::runtime_error(
2033 std::format(
"SqlStatement::MigrateDirect cannot apply this SQLite schema change directly because it "
2034 "requires a table rebuild (e.g. ALTER COLUMN or a foreign-key change). Apply it through "
2035 "MigrationManager::ApplyPendingMigrations, which runs the rebuild executor.\n Script: {}",
2037 [[maybe_unused]]
auto cursor = ExecuteDirect(query, location);
2041template <
typename T>
2042 requires(!std::same_as<T, SqlVariant>)
2043inline std::optional<T> SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
2045 auto cursor = ExecuteDirect(query, location);
2046 RequireSuccess(FetchRow());
2047 return GetNullableColumn<T>(1);
2050template <
typename T>
2051 requires(std::same_as<T, SqlVariant>)
2052inline T SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
2054 auto cursor = ExecuteDirect(query, location);
2055 RequireSuccess(FetchRow());
2056 if (
auto result = GetNullableColumn<T>(1); result.has_value())
2061template <
typename T>
2062 requires(!std::same_as<T, SqlVariant>)
2063inline std::optional<T> SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2065 return ExecuteDirectScalar<T>(query.ToSql(), location);
2068template <
typename T>
2069 requires(std::same_as<T, SqlVariant>)
2070inline T SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2072 return ExecuteDirectScalar<T>(query.ToSql(), location);
2075inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::CloseCursor() noexcept
2080 ResetPrefetchState();
2094 auto const rc = SQLMoreResults(m_hStmt);
2095 if (rc == SQL_NO_DATA || !SQL_SUCCEEDED(rc))
2098 SQLFreeStmt(m_hStmt, SQL_CLOSE);
2099 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.
Requires that T maps onto a column of its record's table.
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.