7#include "DataBinder/Core.hpp"
8#include "DataBinder/SqlDate.hpp"
9#include "DataBinder/SqlDateTime.hpp"
10#include "DataBinder/SqlFixedString.hpp"
11#include "DataBinder/SqlGuid.hpp"
12#include "DataBinder/SqlNumeric.hpp"
13#include "DataBinder/StringInterface.hpp"
14#include "DataBinder/UnicodeConverter.hpp"
15#include "DataMapper/Record.hpp"
16#include "SqlConnection.hpp"
17#include "SqlOdbcPrelude.hpp"
18#include "SqlQuery.hpp"
19#include "SqlQueryFormatter.hpp"
20#include "SqlServerType.hpp"
21#include "TracyProfiler.hpp"
32#include <source_location>
49template <
typename QueryObject>
51 { queryObject.ToSql() } -> std::convertible_to<std::string>;
55class SqlVariantRowCursor;
90 [[nodiscard]] LIGHTWEIGHT_API
bool IsAlive() const noexcept;
93 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrepared() 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 {};
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;
928 _connection { &conn }
937 _connection { &conn },
938 _queryCustomizer { std::move(queryCustomizer) }
945 using difference_type = bool;
946 using value_type = T;
948 iterator& operator++()
952 _is_end = !_cursor->FetchRow();
959 LIGHTWEIGHT_FORCE_INLINE value_type operator*() noexcept
967 SQLUSMALLINT columnIndex = 0;
969 if constexpr (RecordColumnMember<FieldType>)
972 if constexpr (FieldWithStorage<FieldType>)
973 value = _cursor->GetColumn<
typename FieldType::ValueType>(columnIndex);
975 value = _cursor->GetColumn<FieldType>(columnIndex);
982 LIGHTWEIGHT_FORCE_INLINE
constexpr bool operator!=(iterator
const& other)
const noexcept
984 return _is_end != other._is_end;
987 constexpr iterator(std::default_sentinel_t )
noexcept:
989 _cursor { std::nullopt }
993 explicit iterator(SqlConnection& conn):
994 _stmt { std::make_unique<SqlStatement>(conn) },
995 _cursor { std::nullopt }
999 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
1004 void SetCursor(SqlResultCursor cursor)
noexcept
1006 _cursor.emplace(std::move(cursor));
1010 bool _is_end =
false;
1011 std::unique_ptr<SqlStatement> _stmt;
1012 std::optional<SqlResultCursor> _cursor;
1018 auto it = iterator { *_connection };
1019 auto& stmt = it.Statement();
1020 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().Build(_queryCustomizer).All());
1021 it.SetCursor(stmt.Execute());
1029 return iterator { std::default_sentinel };
1039inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsAlive() const noexcept
1041 return m_connection && m_connection->
IsAlive() && m_hStmt !=
nullptr;
1044inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsPrepared() const noexcept
1046 return !m_preparedQuery.empty();
1049inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection& SqlStatement::Connection() noexcept
1051 return *m_connection;
1054inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection const& SqlStatement::Connection() const noexcept
1056 return *m_connection;
1059inline LIGHTWEIGHT_FORCE_INLINE
SqlErrorInfo SqlStatement::LastError()
const
1061 return SqlErrorInfo::FromStatementHandle(m_hStmt);
1064inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
1069inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::Prepare(
SqlQueryObject auto const& queryObject) &
1071 Prepare(queryObject.ToSql());
1076 return Prepare(queryObject.ToSql());
1079inline LIGHTWEIGHT_FORCE_INLINE std::string
const& SqlStatement::PreparedQuery() const noexcept
1081 return m_preparedQuery;
1085template <SqlOutputColumnBinder... Args>
1086inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumns(Args*... args)
1088 if (ShouldRecordPrefetchBinding())
1093 ResetPrefetchBindings();
1095 ((++i, RecordPrefetchOutputColumn<Args>(i, args)), ...);
1099 RequireIndicators();
1102 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *
this))), ...);
1105template <
typename... Records>
1106 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
1107void SqlStatement::BindOutputColumnsToRecord(Records*... records)
1109 if (ShouldRecordPrefetchBinding())
1111 ResetPrefetchBindings();
1114 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1119 this->RecordPrefetchOutputColumn<FieldType>(i, &value);
1126 RequireIndicators();
1130 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1132 if constexpr (RecordColumnMember<FieldType>)
1135 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
1136 m_hStmt, i, &value, GetIndicatorForColumn(i), *
this));
1143template <SqlOutputColumnBinder T>
1144inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
1148 if (ShouldRecordPrefetchBinding())
1150 RecordPrefetchOutputColumn<T>(columnIndex, arg);
1154 RequireIndicators();
1156 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *
this));
1160template <SqlInputParameterBinder Arg>
1161inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg
const& arg)
1164 m_expectedParameterCount = (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)();
1165 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt,
static_cast<SQLUSMALLINT
>(columnIndex), arg, *
this));
1169template <SqlInputParameterBinder Arg,
typename ColumnName>
1170inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
1172 ColumnName&& columnNameHint)
1174 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
1175 BindInputParameter(columnIndex, arg);
1178template <SqlInputParameterBinder... Args>
1185 ZoneScopedN(
"SqlStatement::Execute");
1186 ZoneTextObject(m_preparedQuery);
1187 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1189 if (!(m_expectedParameterCount == (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)()
1190 &&
sizeof...(args) == 0)
1191 && !(m_expectedParameterCount ==
sizeof...(args)))
1192 throw std::invalid_argument {
"Invalid argument count" };
1196 SqlLogger::GetLogger().OnBindInputParameter({}, args),
1197 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *
this))),
1200 auto const result = SQLExecute(m_hStmt);
1202 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
1203 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
1205 ProcessPostExecuteCallbacks();
1210template <
typename T>
1211concept SqlNativeContiguousValueConcept =
1212 std::same_as<T, bool>
1213 || std::same_as<T, char>
1214 || std::same_as<T, unsigned char>
1215 || std::same_as<T, wchar_t>
1216 || std::same_as<T, std::int16_t>
1217 || std::same_as<T, std::uint16_t>
1218 || std::same_as<T, std::int32_t>
1219 || std::same_as<T, std::uint32_t>
1220 || std::same_as<T, std::int64_t>
1221 || std::same_as<T, std::uint64_t>
1222 || std::same_as<T, float>
1223 || std::same_as<T, double>
1224 || std::same_as<T, SqlDate>
1225 || std::same_as<T, SqlTime>
1226 || std::same_as<T, SqlDateTime>
1227 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
1229template <
typename FirstColumnBatch,
typename... MoreColumnBatches>
1230concept SqlNativeBatchable =
1231 std::ranges::contiguous_range<FirstColumnBatch>
1232 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
1233 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
1234 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
1241template <
typename V>
1247template <
typename V>
1253template <
typename V>
1266template <
typename V>
1275template <
typename V>
1277 requires(SQLHSTMT stmt, SQLUSMALLINT column, V
const* elem0, std::size_t n, SqlDataBinderCallback& cb) {
1278 { SqlDataBinder<V>::BatchRowWiseInputParameter(stmt, column, elem0, n, n, cb) } -> std::same_as<SQLRETURN>;
1281template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
1283 MoreColumnBatches
const&... moreColumnBatches)
1285 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
1286 "Must be a supported native contiguous element type.");
1288 ZoneScopedN(
"SqlStatement::ExecuteBatchNative");
1289 ZoneTextObject(m_preparedQuery);
1291 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1292 throw std::invalid_argument {
"Invalid number of columns" };
1294 auto const rowCount = std::ranges::size(firstColumnBatch);
1295 ZoneValue(rowCount);
1296 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1297 throw std::invalid_argument {
"Uneven number of rows" };
1299 size_t rowStart = 0;
1303 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1304 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
1305 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
1306 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1307 ClearBatchIndicators();
1308 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(firstColumnBatch))>>::
1309 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *
this));
1310 SQLUSMALLINT column = 1;
1311 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(moreColumnBatches))>>::
1312 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *
this)),
1314 RequireSuccess(SQLExecute(m_hStmt));
1315 ProcessPostExecuteCallbacks();
1321template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1322inline LIGHTWEIGHT_FORCE_INLINE
SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch
const& firstColumnBatch,
1323 MoreColumnBatches
const&... moreColumnBatches)
1327 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
1328 return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
1330 return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
1333template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1335 MoreColumnBatches
const&... moreColumnBatches)
1337 ZoneScopedN(
"SqlStatement::ExecuteBatchSoft");
1338 ZoneTextObject(m_preparedQuery);
1340 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1341 throw std::invalid_argument {
"Invalid number of columns" };
1343 auto const rowCount = std::ranges::size(firstColumnBatch);
1344 ZoneValue(rowCount);
1345 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1346 throw std::invalid_argument {
"Uneven number of rows" };
1348 for (
auto const rowIndex: std::views::iota(
size_t { 0 }, rowCount))
1351 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues
const&... columnsInRow) {
1352 SQLUSMALLINT column = 0;
1353 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
1354 RequireSuccess(SQLExecute(m_hStmt));
1355 ProcessPostExecuteCallbacks();
1358 std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch),
static_cast<std::ptrdiff_t
>(rowIndex))),
1360 *std::ranges::next(std::ranges::begin(moreColumnBatches),
static_cast<std::ptrdiff_t
>(rowIndex)))...));
1365template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1366 requires(
sizeof...(ColumnAccessors) >= 1
1367 && (std::invocable<ColumnAccessors
const&, std::ranges::range_value_t<Rows>
const&> && ...))
1368SqlResultCursor SqlStatement::ExecuteBatch(Rows
const& rows, ColumnAccessors
const&... accessors)
1370 ZoneScopedN(
"SqlStatement::ExecuteBatch(row-major)");
1371 ZoneTextObject(m_preparedQuery);
1373 using RowElem = std::ranges::range_value_t<Rows>;
1375 auto const rowCount = std::ranges::size(rows);
1379 if (m_expectedParameterCount !=
static_cast<SQLSMALLINT
>(
sizeof...(accessors)))
1380 throw std::invalid_argument {
"Invalid number of columns" };
1386 constexpr bool allColumnsRowBindable =
1388 constexpr bool allAccessorsReturnReference =
1389 (std::is_reference_v<std::invoke_result_t<ColumnAccessors const&, RowElem const&>> && ...);
1390 constexpr bool anyStridedIndicatorColumn =
1392 constexpr bool indicatorAlignmentSatisfied = (
sizeof(RowElem) %
alignof(SQLLEN)) == 0;
1394 if constexpr (allColumnsRowBindable && allAccessorsReturnReference
1395 && (!anyStridedIndicatorColumn || indicatorAlignmentSatisfied))
1397 auto const* rowData = std::ranges::data(rows);
1401 auto const accessorStrideMatchesRow = [&](
auto const& accessor)
noexcept ->
bool {
1402 auto const* first =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[0])));
1403 auto const* second =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[1])));
1404 return static_cast<std::size_t
>(second - first) ==
sizeof(RowElem);
1406 bool const rowStrideOk = rowCount < 2 || (accessorStrideMatchesRow(accessors) && ...);
1408 if (m_connection->SupportsNativeRowBatch() && rowStrideOk)
1409 return ExecuteBatchNativeRowWise(rows, accessors...);
1412 return ExecuteBatchSoftRowMajor(rows, accessors...);
1415template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1416SqlResultCursor SqlStatement::ExecuteBatchNativeRowWise(Rows
const& rows, ColumnAccessors
const&... accessors)
1418 ZoneScopedN(
"SqlStatement::ExecuteBatchNativeRowWise");
1419 ZoneTextObject(m_preparedQuery);
1421 using RowElem = std::ranges::range_value_t<Rows>;
1422 auto const rowCount = std::ranges::size(rows);
1423 ZoneValue(rowCount);
1424 auto const* rowData = std::ranges::data(rows);
1428 SQLULEN processedCount = rowCount;
1434 auto const restoreParameterBinding = detail::Finally([
this] {
1435 ResetParameterArrayBinding();
1436 ClearBatchIndicators();
1442 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1444 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)
sizeof(RowElem), 0));
1445 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
nullptr, 0));
1446 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1447 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &processedCount, 0));
1450 SQLUSMALLINT column = 0;
1451 auto const bindColumn = [&](
auto const& accessor) {
1453 using ValueType = std::remove_cvref_t<
decltype(accessor(rowData[0]))>;
1456 if constexpr (SqlHasRowWiseBatchBinder<ValueType>)
1457 RequireSuccess(SqlDataBinder<ValueType>::BatchRowWiseInputParameter(
1458 m_hStmt, column, std::addressof(accessor(rowData[0])),
sizeof(RowElem), rowCount, *
this));
1460 RequireSuccess(SqlDataBinder<ValueType>::InputParameter(m_hStmt, column, accessor(rowData[0]), *
this));
1462 (bindColumn(accessors), ...);
1464 SqlLogger::GetLogger().OnExecuteBatch();
1467 auto const executeResult = SQLExecute(m_hStmt);
1468 RequireSuccessfulBatchExecute(executeResult, processedCount,
static_cast<SQLULEN
>(rowCount));
1469 ProcessPostExecuteCallbacks();
1471 return SqlResultCursor { *
this };
1474template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1475SqlResultCursor SqlStatement::ExecuteBatchSoftRowMajor(Rows
const& rows, ColumnAccessors
const&... accessors)
1477 ZoneScopedN(
"SqlStatement::ExecuteBatchSoftRowMajor");
1478 ZoneTextObject(m_preparedQuery);
1480 auto const* rowData = std::ranges::data(rows);
1481 auto const rowCount = std::ranges::size(rows);
1482 ZoneValue(rowCount);
1484 for (
auto const rowIndex: std::views::iota(std::size_t { 0 }, rowCount))
1486 auto const& row = rowData[rowIndex];
1487 SQLUSMALLINT column = 0;
1489 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(accessors(row))>>::InputParameter(
1490 m_hStmt, column, accessors(row), *
this))),
1492 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1493 RequireExecuteSucceededOrNoData(SQLExecute(m_hStmt));
1494 ProcessPostExecuteCallbacks();
1497 return SqlResultCursor { *
this };
1500template <
typename Value>
1501void SqlStatement::BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators)
1503 if constexpr (IsSqlFixedString<Value>)
1510 RequireSuccess(SQLBindCol(m_hStmt,
1513 (SQLPOINTER) SqlBasicStringOperations<Value>::Data(
static_cast<Value*
>(base0)),
1514 static_cast<SQLLEN
>(Value::Capacity) + 1,
1521 RequireSuccess(SqlDataBinder<Value>::OutputColumn(m_hStmt, column,
static_cast<Value*
>(base0), indicators, *
this));
1525template <
typename ValueType>
1526SQLLEN* SqlStatement::BindRowWiseOutputColumn(SQLUSMALLINT column,
void* base0, std::size_t rowStride, std::size_t depth)
1532 auto*
const indicatorBytes = ProvideBatchStagingBuffer(((depth - 1) * rowStride) +
sizeof(SQLLEN));
1533 auto*
const indicators =
reinterpret_cast<SQLLEN*
>(indicatorBytes);
1535 if constexpr (SqlIsStdOptional<ValueType>)
1537 using Inner = ValueType::value_type;
1538 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1541 for (
auto const i: std::views::iota(std::size_t { 0 }, depth))
1542 reinterpret_cast<ValueType*>(optBytes + (i * rowStride))->emplace();
1545 auto*
const contained0 =
reinterpret_cast<Inner*
>(optBytes + detail::OptionalValueOffset<Inner>());
1546 BindRowWiseValue<Inner>(column, contained0, indicators);
1550 BindRowWiseValue<ValueType>(column, base0, indicators);
1555template <
typename ValueType>
1556void SqlStatement::FinalizeRowWiseOutputColumn(
void* base0,
1557 std::size_t rowStride,
1558 std::size_t rowCount,
1559 SQLLEN
const* indicators)
noexcept
1561 auto const indicatorAt = [&](std::size_t i)
noexcept {
1562 return *
reinterpret_cast<SQLLEN const*
>(
reinterpret_cast<std::byte const*
>(indicators) + (i * rowStride));
1565 if constexpr (SqlIsStdOptional<ValueType>)
1567 using Inner = ValueType::value_type;
1568 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1569 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1571 auto*
const optional =
reinterpret_cast<ValueType*
>(optBytes + (i * rowStride));
1572 if (indicatorAt(i) == SQL_NULL_DATA)
1574 else if constexpr (IsSqlFixedString<Inner>)
1580 if (optional->has_value())
1581 SqlBasicStringOperations<Inner>::PostProcessOutputColumn(std::addressof(**optional), indicatorAt(i));
1586 else if constexpr (IsSqlFixedString<ValueType>)
1588 auto*
const base =
static_cast<std::byte*
>(base0);
1589 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1590 SqlBasicStringOperations<ValueType>::PostProcessOutputColumn(
1591 reinterpret_cast<ValueType*>(base + (i * rowStride)), indicatorAt(i));
1597template <
typename Record,
typename... ColumnAccessors>
1598void SqlStatement::FetchAllRowWise(std::vector<Record>& out, std::size_t arrayDepth, ColumnAccessors
const&... accessors)
1600 ZoneScopedN(
"SqlStatement::FetchAllRowWise");
1601 ZoneTextObject(m_preparedQuery);
1603 static_assert(
sizeof...(ColumnAccessors) >= 1,
"FetchAllRowWise requires at least one column accessor");
1604 constexpr std::size_t columnCount =
sizeof...(ColumnAccessors);
1611 auto const perRow =
sizeof(Record) * (1 + columnCount);
1612 auto const budgetDepth = RowArrayCursor::MemoryBudgetBytes / std::max<std::size_t>(perRow, 1);
1613 auto const minDepth = std::min(RowArrayCursor::MinArrayDepth, arrayDepth);
1614 arrayDepth = std::clamp(budgetDepth, minDepth, arrayDepth);
1617 std::vector<SQLUSMALLINT> rowStatus(arrayDepth);
1618 SQLULEN rowsFetched = 0;
1623 auto const restoreFetchState = detail::Finally([
this] {
1624 SQLFreeStmt(m_hStmt, SQL_UNBIND);
1627 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, 0);
1628 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
1629 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR,
nullptr, 0);
1630 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR,
nullptr, 0);
1632 ClearBatchIndicators();
1637 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)
sizeof(Record), 0));
1639 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arrayDepth, 0));
1640 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, rowStatus.data(), 0));
1641 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowsFetched, 0));
1646 std::size_t
const base = out.size();
1647 out.resize(base + arrayDepth);
1648 Record*
const row0 = out.data() + base;
1652 ClearBatchIndicators();
1653 std::array<SQLLEN*, columnCount> indicators {};
1654 SQLUSMALLINT column = 0;
1655 std::size_t bindIndex = 0;
1656 ((indicators[bindIndex++] = BindRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1657 ++column, std::addressof(accessors(*row0)),
sizeof(Record), arrayDepth)),
1661 auto const fetchResult = SQLFetchScroll(m_hStmt, SQL_FETCH_NEXT, 0);
1662 if (fetchResult == SQL_NO_DATA)
1669 if (!SQL_SUCCEEDED(fetchResult))
1670 RequireSuccess(fetchResult);
1672 auto const fetched =
static_cast<std::size_t
>(rowsFetched);
1673 SqlLogger::GetLogger().OnFetchRow();
1675 std::size_t finalizeIndex = 0;
1676 (FinalizeRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1677 std::addressof(accessors(*row0)),
sizeof(Record), fetched, indicators[finalizeIndex++]),
1680 out.resize(base + fetched);
1681 if (fetched < arrayDepth)
1685 SqlLogger::GetLogger().OnFetchEnd();
1688template <SqlGetColumnNativeType T>
1689inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result)
const
1691 if (IsPrefetchActive())
1693 auto const& cursor = PrefetchCursorRef();
1694 auto const row = PrefetchRowInBlock();
1695 RequirePrefetchColumnInRange(cursor, column);
1696 if (cursor.IsCellNull(row, column))
1698 *result = ConvertCell<T>(cursor, row, column);
1701 SQLLEN indicator {};
1702 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *
this));
1703 return indicator != SQL_NULL_DATA;
1709 template <
typename T>
1710 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
1713 template <
typename T>
1714 struct IsSqlFixedStringSpec: std::false_type
1717 template <std::
size_t N,
typename Char, SqlFixedStringMode Mode>
1718 struct IsSqlFixedStringSpec<SqlFixedString<N, Char, Mode>>: std::true_type
1721 template <
typename T>
1722 concept SqlFixedStringCell = IsSqlFixedStringSpec<std::remove_cvref_t<T>>::value;
1725 template <
typename T>
1726 concept PlainStringCell =
1727 std::same_as<T, std::string> || std::same_as<T, std::u8string> || std::same_as<T, std::u16string>
1728 || std::same_as<T, std::u32string> || std::same_as<T, std::wstring>;
1731 template <
typename T>
1732 struct IsSqlNumericSpec: std::false_type
1735 template <std::
size_t Precision, std::
size_t Scale>
1736 struct IsSqlNumericSpec<SqlNumeric<Precision, Scale>>: std::true_type
1739 template <
typename T>
1740 concept SqlNumericCell = IsSqlNumericSpec<std::remove_cvref_t<T>>::value;
1743 [[nodiscard]]
inline std::u8string_view AsU8View(std::string
const& utf8)
noexcept
1745 return std::u8string_view {
reinterpret_cast<char8_t const*
>(utf8.data()), utf8.size() };
1757 template <SqlFixedStringMode Mode>
1758 inline void TrimFixedStringBytes(std::string& bytes)
noexcept
1760 auto const isTrailingTrimmable = [](
char c)
noexcept {
1763 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
1764 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
1768 while (!bytes.empty() && isTrailingTrimmable(bytes.back()))
1778 template <
typename Char>
1779 [[nodiscard]]
inline std::basic_string<Char> DecodeUtf8To(std::string
const& utf8)
1781 if constexpr (std::same_as<Char, char>)
1783 else if constexpr (std::same_as<Char, char8_t>)
1784 return std::u8string { AsU8View(utf8) };
1785 else if constexpr (std::same_as<Char, char16_t>)
1786 return ToUtf16(AsU8View(utf8));
1787 else if constexpr (std::same_as<Char, char32_t>)
1788 return ToUtf32<std::u32string>(AsU8View(utf8));
1796 template <
typename T>
1797 concept StringLikeCell = PlainStringCell<T> || SqlStringInterface<T>;
1802 template <
typename T>
1803 concept PrefetchConvertibleScalar =
1804 std::same_as<T, SqlVariant> || std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>
1805 || StringLikeCell<T> || std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>;
1807 template <
typename T>
1808 struct PrefetchConvertibleOptional: std::false_type
1811 template <
typename U>
1812 struct PrefetchConvertibleOptional<std::optional<U>>: std::bool_constant<PrefetchConvertibleScalar<U>>
1817 template <
typename T>
1818 concept PrefetchConvertible = PrefetchConvertibleScalar<T> || PrefetchConvertibleOptional<T>::value;
1823 template <
typename T>
1824 [[nodiscard]]
inline T ReadTemporalGuidCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1826 using BoundType = RowArrayCursor::BoundType;
1827 auto const boundType = cursor.ColumnBoundType(column);
1828 if constexpr (std::same_as<T, SqlDate>)
1829 return boundType == BoundType::Date ? cursor.GetDate(row, column).value_or(SqlDate {}) : SqlDate {};
1830 else if constexpr (std::same_as<T, SqlDateTime>)
1831 return boundType == BoundType::Timestamp ? cursor.GetTimestamp(row, column).value_or(SqlDateTime {})
1835 if (boundType == BoundType::Guid)
1836 return cursor.GetGuid(row, column).value_or(SqlGuid {});
1837 if (boundType == BoundType::Char || boundType == BoundType::WChar)
1838 return SqlGuid::TryParse(cursor.GetString(row, column).value_or(std::string {})).value_or(SqlGuid {});
1845 template <
typename T>
1846 [[nodiscard]]
inline T ReadNumericCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1848 using BoundType = RowArrayCursor::BoundType;
1849 switch (cursor.ColumnBoundType(column))
1851 case BoundType::Double:
1852 return T { cursor.GetF64(row, column).value_or(0.0) };
1853 case BoundType::Int64:
1854 return T {
static_cast<double>(cursor.GetI64(row, column).value_or(0)) };
1866 [[nodiscard]]
inline std::string RenderCellAsUtf8(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1868 switch (cursor.ColumnBoundType(column))
1870 case RowArrayCursor::BoundType::Char:
1871 case RowArrayCursor::BoundType::WChar:
1872 return cursor.GetString(row, column).value_or(std::string {});
1873 case RowArrayCursor::BoundType::Int64:
1874 return std::format(
"{}", cursor.GetI64(row, column).value_or(0));
1875 case RowArrayCursor::BoundType::Double:
1876 return std::format(
"{}", cursor.GetF64(row, column).value_or(0.0));
1877 case RowArrayCursor::BoundType::Date:
1878 return std::format(
"{}", cursor.GetDate(row, column).value_or(SqlDate {}));
1879 case RowArrayCursor::BoundType::Timestamp:
1880 return std::format(
"{}", cursor.GetTimestamp(row, column).value_or(SqlDateTime {}));
1881 case RowArrayCursor::BoundType::Guid:
1882 return std::format(
"{}", cursor.GetGuid(row, column).value_or(SqlGuid {}));
1884 return std::string {};
1892 template <
typename T>
1893 [[nodiscard]]
inline T ReadStringLikeCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1895 auto utf8 = RenderCellAsUtf8(cursor, row, column);
1896 if constexpr (SqlFixedStringCell<T>)
1897 TrimFixedStringBytes<T::PostRetrieveOperation>(utf8);
1898 return T { DecodeUtf8To<typename T::value_type>(utf8) };
1904 template <
typename T>
1905 [[nodiscard]]
inline T ReadArithmeticCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1907 using BoundType = RowArrayCursor::BoundType;
1908 switch (cursor.ColumnBoundType(column))
1910 case BoundType::Int64:
1911 return static_cast<T
>(cursor.GetI64(row, column).value_or(0));
1912 case BoundType::Double:
1913 return static_cast<T
>(cursor.GetF64(row, column).value_or(0.0));
1921template <
typename T>
1922inline T SqlStatement::ConvertCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
const
1927 if constexpr (std::same_as<T, SqlVariant>)
1928 return MakePrefetchVariantCell(cursor, row, column);
1929 else if constexpr (IsSpecializationOf<std::optional, T>)
1931 if (cursor.IsCellNull(row, column))
1932 return std::nullopt;
1933 return T { ConvertCell<typename T::value_type>(cursor, row, column) };
1935 else if constexpr (std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>)
1936 return detail::ReadTemporalGuidCell<T>(cursor, row, column);
1937 else if constexpr (detail::SqlNumericCell<T>)
1938 return detail::ReadNumericCell<T>(cursor, row, column);
1939 else if constexpr (detail::StringLikeCell<T>)
1940 return detail::ReadStringLikeCell<T>(cursor, row, column);
1941 else if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>)
1942 return detail::ReadArithmeticCell<T>(cursor, row, column);
1950template <SqlOutputColumnBinder T>
1951inline void SqlStatement::RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg)
1953 auto deferredBind = [
this, column, arg] {
1954 RequireIndicators();
1955 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, column, arg, GetIndicatorForColumn(column), *
this));
1957 if constexpr (detail::PrefetchConvertible<T>)
1959 RecordPrefetchColumn(
1961 [
this, column, arg] { *arg = ConvertCell<T>(PrefetchCursorRef(), PrefetchRowInBlock(), column); },
1962 std::move(deferredBind));
1968 RecordPrefetchColumn(column, {}, std::move(deferredBind));
1969 MarkPrefetchBindingUnsupported();
1973template <SqlGetColumnNativeType T>
1974inline T SqlStatement::GetColumn(SQLUSMALLINT column)
const
1976 if (IsPrefetchActive())
1978 auto const& cursor = PrefetchCursorRef();
1979 auto const row = PrefetchRowInBlock();
1980 RequirePrefetchColumnInRange(cursor, column);
1981 if constexpr (!detail::SqlNullableType<T>)
1982 if (cursor.IsCellNull(row, column))
1983 throw std::runtime_error {
"Column value is NULL" };
1984 return ConvertCell<T>(cursor, row, column);
1987 SQLLEN indicator {};
1992 ZoneScopedN(
"SqlStatement::ColumnGetData");
1993 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
1995 if constexpr (!detail::SqlNullableType<T>)
1996 if (indicator == SQL_NULL_DATA)
1997 throw std::runtime_error {
"Column value is NULL" };
2001template <SqlGetColumnNativeType T>
2002inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column)
const
2004 if (IsPrefetchActive())
2006 auto const& cursor = PrefetchCursorRef();
2007 auto const row = PrefetchRowInBlock();
2008 RequirePrefetchColumnInRange(cursor, column);
2009 if (cursor.IsCellNull(row, column))
2010 return std::nullopt;
2011 return ConvertCell<T>(cursor, row, column);
2014 SQLLEN indicator {};
2016 ZoneScopedN(
"SqlStatement::ColumnGetData");
2017 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
2019 if (indicator == SQL_NULL_DATA)
2020 return std::nullopt;
2021 return { std::move(result) };
2024template <SqlGetColumnNativeType T>
2025T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
2027 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
2031 std::source_location location)
2033 return ExecuteDirect(query.ToSql(), location);
2036template <
typename Callable>
2037 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
2038void SqlStatement::MigrateDirect(Callable
const& callable, std::source_location location)
2040 ZoneScopedN(
"SqlStatement::MigrateDirect");
2042 callable(migration);
2043 auto const queries = migration.GetPlan().ToSql();
2044 ZoneValue(queries.size());
2050 auto const isCommentOnlyGuardScript = [](std::string_view script) {
2051 constexpr std::string_view marker =
"-- LIGHTWEIGHT_SQLITE_GUARD:";
2052 if (!script.starts_with(marker))
2054 auto const newline = script.find(
'\n');
2055 if (newline == std::string_view::npos)
2057 auto const body = script.substr(newline + 1);
2058 auto const bodyStart = body.find_first_not_of(
" \t\r\n");
2059 return bodyStart == std::string_view::npos || body.substr(bodyStart).starts_with(
"--");
2062 for (
auto const& query: queries)
2064 if (isCommentOnlyGuardScript(query))
2065 throw std::runtime_error(
2066 std::format(
"SqlStatement::MigrateDirect cannot apply this SQLite schema change directly because it "
2067 "requires a table rebuild (e.g. ALTER COLUMN or a foreign-key change). Apply it through "
2068 "MigrationManager::ApplyPendingMigrations, which runs the rebuild executor.\n Script: {}",
2070 [[maybe_unused]]
auto cursor = ExecuteDirect(query, location);
2074template <
typename T>
2075 requires(!std::same_as<T, SqlVariant>)
2076inline std::optional<T> SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
2078 auto cursor = ExecuteDirect(query, location);
2079 RequireSuccess(FetchRow());
2080 return GetNullableColumn<T>(1);
2083template <
typename T>
2084 requires(std::same_as<T, SqlVariant>)
2085inline T SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
2087 auto cursor = ExecuteDirect(query, location);
2088 RequireSuccess(FetchRow());
2089 if (
auto result = GetNullableColumn<T>(1); result.has_value())
2094template <
typename T>
2095 requires(!std::same_as<T, SqlVariant>)
2096inline std::optional<T> SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2098 return ExecuteDirectScalar<T>(query.ToSql(), location);
2101template <
typename T>
2102 requires(std::same_as<T, SqlVariant>)
2103inline T SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2105 return ExecuteDirectScalar<T>(query.ToSql(), location);
2108inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::CloseCursor() noexcept
2113 ResetPrefetchState();
2127 auto const rc = SQLMoreResults(m_hStmt);
2128 if (rc == SQL_NO_DATA || !SQL_SUCCEEDED(rc))
2131 SQLFreeStmt(m_hStmt, SQL_CLOSE);
2132 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.
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 over all rows of the record's table, using the given SQL connection.
SqlRowIterator(SqlConnection &conn, QueryCustomizer queryCustomizer)
std::function< void(SqlSelectQueryBuilder &)> QueryCustomizer
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.
Query builder for building SELECT ... queries.
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)
One column pair of a composite foreign key: "this record's column references that one".
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.