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 "SqlPreparedStatementCache.hpp"
19#include "SqlQuery.hpp"
20#include "SqlQueryFormatter.hpp"
21#include "SqlServerType.hpp"
22#include "SqlStatistics.hpp"
23#include "TracyProfiler.hpp"
34#include <source_location>
52template <
typename QueryObject>
54 { queryObject.ToSql() } -> std::convertible_to<std::string>;
62template <
typename QueryObject>
64 { queryObject.ProjectedFieldNames() } -> std::convertible_to<std::span<std::string const>>;
65 { queryObject.ProjectionHasWildcard() } -> std::convertible_to<bool>;
69class SqlVariantRowCursor;
104 [[nodiscard]] LIGHTWEIGHT_API
bool IsAlive() const noexcept;
107 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrepared() const noexcept;
116 [[nodiscard]] LIGHTWEIGHT_API
SqlErrorInfo LastError() const;
123 std::string_view
const& tableAlias)
const;
126 [[nodiscard]] LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept;
132 LIGHTWEIGHT_API
void Prepare(std::string_view query) &;
147 [[nodiscard]] std::
string const& PreparedQuery() const noexcept;
166 template <SqlInputParameterBinder Arg>
167 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg);
170 template <SqlInputParameterBinder Arg, typename ColumnName>
171 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint);
174 template <SqlInputParameterBinder... Args>
188 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
189 [[nodiscard]]
SqlResultCursor ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch,
190 MoreColumnBatches const&... moreColumnBatches);
200 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
201 [[nodiscard]]
SqlResultCursor ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch,
202 MoreColumnBatches const&... moreColumnBatches);
209 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
211 MoreColumnBatches const&... moreColumnBatches);
241 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
242 requires(sizeof...(ColumnAccessors) >= 1
243 && (std::invocable<ColumnAccessors const&, std::ranges::range_value_t<Rows> const&> && ...))
244 [[nodiscard]]
SqlResultCursor ExecuteBatch(Rows const& rows, ColumnAccessors const&... accessors);
248 ExecuteDirect(std::string_view const& query, std::source_location location = std::source_location::current());
252 std::source_location location = std::source_location::current());
266 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor ExecuteBatchFetch(std::string_view query, std::
size_t arrayDepth);
269 template <typename Callable>
271 void MigrateDirect(Callable const& callable, std::source_location location = std::source_location::current());
275 template <typename T>
277 [[nodiscard]] std::optional<T> ExecuteDirectScalar(std::string_view const& query,
278 std::source_location location = std::source_location::current());
281 template <typename T>
283 [[nodiscard]] T ExecuteDirectScalar(std::string_view const& query,
284 std::source_location location = std::source_location::current());
288 template <typename T>
290 [[nodiscard]] std::optional<T> ExecuteDirectScalar(
SqlQueryObject auto const& query,
291 std::source_location location = std::source_location::current());
294 template <typename T>
296 [[nodiscard]] T ExecuteDirectScalar(
SqlQueryObject auto const& query,
297 std::source_location location = std::source_location::current());
300 [[nodiscard]] LIGHTWEIGHT_API
size_t LastInsertId(std::string_view tableName);
306 [[nodiscard]] LIGHTWEIGHT_API
size_t NumRowsAffected() const;
307 [[nodiscard]] LIGHTWEIGHT_API
size_t NumColumnsAffected() const;
308 [[nodiscard]] LIGHTWEIGHT_API
bool FetchRow();
309 [[nodiscard]] LIGHTWEIGHT_API std::expected<
bool,
SqlErrorInfo> TryFetchRow(
310 std::source_location location = std::source_location::current()) noexcept;
311 void CloseCursor() noexcept;
316 template <SqlOutputColumnBinder... Args>
317 void BindOutputColumns(Args*... args);
323 template <typename... Records>
324 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
325 void BindOutputColumnsToRecord(Records*... records);
331 template <SqlOutputColumnBinder T>
332 void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg);
334 template <SqlGetColumnNativeType T>
335 [[nodiscard]]
bool GetColumn(SQLUSMALLINT column, T* result) const;
337 template <SqlGetColumnNativeType T>
338 [[nodiscard]] T GetColumn(SQLUSMALLINT column) const;
342 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
343 [[nodiscard]]
SqlResultCursor ExecuteBatchNativeRowWise(Rows const& rows, ColumnAccessors const&... accessors);
347 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
348 [[nodiscard]]
SqlResultCursor ExecuteBatchSoftRowMajor(Rows const& rows, ColumnAccessors const&... accessors);
365 template <typename Record, typename... ColumnAccessors>
366 void FetchAllRowWise(std::vector<Record>& out, std::
size_t arrayDepth, ColumnAccessors const&... accessors);
371 template <typename ValueType>
372 [[nodiscard]] SQLLEN* BindRowWiseOutputColumn(SQLUSMALLINT column,
374 std::
size_t rowStride,
381 template <typename Value>
382 void BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators);
386 template <typename ValueType>
387 static
void FinalizeRowWiseOutputColumn(
void* base0,
388 std::
size_t rowStride,
389 std::
size_t rowCount,
390 SQLLEN const* indicators) noexcept;
392 template <SqlGetColumnNativeType T>
393 [[nodiscard]] std::optional<T> GetNullableColumn(SQLUSMALLINT column) const;
395 template <SqlGetColumnNativeType T>
396 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const;
410 [[nodiscard]] LIGHTWEIGHT_API SQLUSMALLINT ResolveColumnName(std::string_view name) const;
413 [[nodiscard]] LIGHTWEIGHT_API std::
string DescribeProjectedFieldNames() const;
415 LIGHTWEIGHT_API
void RequireSuccess(SQLRETURN error,
416 std::source_location sourceLocation = std::source_location::current()) const;
417 LIGHTWEIGHT_API
void PlanPostExecuteCallback(std::function<
void()>&& cb) override;
418 LIGHTWEIGHT_API
void PlanPostProcessOutputColumn(std::function<
void()>&& cb) override;
419 [[nodiscard]] LIGHTWEIGHT_API SqlServerType ServerType() const noexcept override;
420 [[nodiscard]] LIGHTWEIGHT_API std::
string const& DriverName() const noexcept override;
421 LIGHTWEIGHT_API
void ProcessPostExecuteCallbacks();
423 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicator() override;
424 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicators(
size_t rowCount) override;
425 LIGHTWEIGHT_API std::
byte* ProvideBatchStagingBuffer(std::
size_t byteCount) override;
430 [[nodiscard]] LIGHTWEIGHT_API std::optional<SQLSMALLINT> DescribeInputParameterType(
431 SQLUSMALLINT column) noexcept override;
432 LIGHTWEIGHT_API
void ClearBatchIndicators();
435 LIGHTWEIGHT_API
void ResetParameterArrayBinding() noexcept;
438 LIGHTWEIGHT_API
void RequireExecuteSucceededOrNoData(
439 SQLRETURN result, std::source_location sourceLocation = std::source_location::current()) const;
442 LIGHTWEIGHT_API
void RequireSuccessfulBatchExecute(
444 SQLULEN processedCount,
445 SQLULEN expectedCount,
446 std::source_location sourceLocation = std::source_location::current()) const;
463 [[nodiscard]] LIGHTWEIGHT_API
bool RetryStalePreparedStatement(SQLRETURN result);
465 LIGHTWEIGHT_API
void RequireIndicators();
466 LIGHTWEIGHT_API SQLLEN* GetIndicatorForColumn(SQLUSMALLINT column) noexcept;
475 [[nodiscard]] std::
size_t EffectivePrefetchDepth() const noexcept;
477 void ArmPrefetchOnFirstFetch() noexcept;
480 [[nodiscard]] std::expected<
bool,
SqlErrorInfo> FetchRowPrefetched() noexcept;
482 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrefetchActive() const noexcept;
484 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor const& PrefetchCursorRef() const noexcept;
486 [[nodiscard]] LIGHTWEIGHT_API std::
size_t PrefetchRowInBlock() const noexcept;
489 [[nodiscard]] LIGHTWEIGHT_API
bool ShouldRecordPrefetchBinding() const noexcept;
491 LIGHTWEIGHT_API
void ResetPrefetchBindings() noexcept;
494 LIGHTWEIGHT_API
void MarkPrefetchBindingUnsupported() noexcept;
503 LIGHTWEIGHT_API
void RecordPrefetchColumn(SQLUSMALLINT column,
504 std::function<
void()> scatter,
505 std::function<
void()> deferredBind);
507 LIGHTWEIGHT_API
void ResetPrefetchState() noexcept;
511 SQLUSMALLINT column) const;
513 template <typename T>
514 [[nodiscard]] T ConvertCell(
RowArrayCursor const& cursor, std::
size_t row, SQLUSMALLINT column) const;
519 LIGHTWEIGHT_API
void RequirePrefetchColumnInRange(
RowArrayCursor const& cursor, SQLUSMALLINT column) const;
523 template <SqlOutputColumnBinder T>
524 void RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg);
531 void AdoptProjectedFieldNames(QueryObject const& queryObject);
556 void PrepareOnHandle(std::
string queryText);
564 [[nodiscard]] std::vector<SQLSMALLINT> DescribeInputParameterTypes() noexcept;
568 bool ReleasePreparedHandle() noexcept;
576 void ReleasePreparedHandleForDirectExecution();
579 void EnsureStatementHandle();
583 std::unique_ptr<Data,
void (*)(Data*)> m_data;
586 std::string m_preparedQuery;
587 std::optional<SQLSMALLINT> m_numColumns;
590 std::vector<std::string> m_projectedFieldNames;
591 bool m_projectionHasWildcard =
false;
592 SQLSMALLINT m_expectedParameterCount {};
597 SQLSMALLINT m_preparedParameterCount {};
598 bool m_reusedPreparedQuery {
false };
600 SqlPreparedStatementCaching::Enabled
606 return m_preparedStatementCaching;
620 SqlResultCursor() =
delete;
621 SqlResultCursor(SqlResultCursor
const&) =
delete;
622 SqlResultCursor& operator=(SqlResultCursor
const&) =
delete;
626 m_stmt { other.m_stmt }
628 other.m_stmt =
nullptr;
636 m_stmt = other.m_stmt;
637 other.m_stmt =
nullptr;
646 m_stmt->CloseCursor();
654 return m_stmt->NumRowsAffected();
660 return m_stmt->NumColumnsAffected();
666 template <SqlOutputColumnBinder... Args>
669 m_stmt->BindOutputColumns(args...);
673 template <SqlOutputColumnBinder T>
676 m_stmt->BindOutputColumn(columnIndex, arg);
680 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool FetchRow()
682 return m_stmt->FetchRow();
686 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::expected<bool, SqlErrorInfo>
TryFetchRow(
687 std::source_location location = std::source_location::current()) noexcept
689 return m_stmt->TryFetchRow(location);
693 template <
typename... Records>
694 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
697 m_stmt->BindOutputColumnsToRecord(records...);
706 template <
typename Record,
typename... ColumnAccessors>
708 std::size_t arrayDepth,
709 ColumnAccessors
const&... accessors)
711 m_stmt->FetchAllRowWise(out, arrayDepth, accessors...);
717 template <SqlGetColumnNativeType T>
718 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool GetColumn(SQLUSMALLINT column, T* result)
const
720 return m_stmt->GetColumn<T>(column, result);
724 template <SqlGetColumnNativeType T>
725 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(SQLUSMALLINT column)
const
727 return m_stmt->GetColumn<T>(column);
733 template <SqlGetColumnNativeType T>
734 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(SQLUSMALLINT column)
const
736 return m_stmt->GetNullableColumn<T>(column);
742 template <SqlGetColumnNativeType T>
743 [[nodiscard]] T
GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
745 return m_stmt->GetColumnOr(column, std::forward<T>(defaultValue));
761 template <SqlGetColumnNativeType T>
762 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(std::string_view name)
const
764 return m_stmt->GetColumn<T>(m_stmt->ResolveColumnName(name));
775 template <SqlGetColumnNativeType T>
776 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(std::string_view name)
const
778 return m_stmt->GetNullableColumn<T>(m_stmt->ResolveColumnName(name));
790 template <SqlGetColumnNativeType T>
791 [[nodiscard]] T
GetColumnOr(std::string_view name, T&& defaultValue)
const
793 return m_stmt->GetColumnOr(m_stmt->ResolveColumnName(name), std::forward<T>(defaultValue));
811 using std::runtime_error::runtime_error;
845 static constexpr std::size_t MaxCharColumnBytes = 8192;
851 static constexpr std::size_t MemoryBudgetBytes = 4 * 1024 * 1024;
855 static constexpr std::size_t MinArrayDepth = 16;
877 [[nodiscard]] LIGHTWEIGHT_API std::
size_t FetchArray();
880 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ColumnCount() const noexcept;
885 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ArrayDepth() const noexcept;
891 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::int64_t> GetI64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
897 [[nodiscard]] LIGHTWEIGHT_API std::optional<
double> GetF64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
910 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::
string> GetString(std::
size_t rowInBatch, SQLUSMALLINT column) const;
916 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDate> GetDate(std::
size_t rowInBatch, SQLUSMALLINT column) const;
923 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDateTime> GetTimestamp(std::
size_t rowInBatch, SQLUSMALLINT column) const;
930 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlGuid> GetGuid(std::
size_t rowInBatch, SQLUSMALLINT column) const;
956 [[nodiscard]] LIGHTWEIGHT_API SQLSMALLINT
ColumnSqlType(SQLUSMALLINT column)
const;
962 [[nodiscard]] LIGHTWEIGHT_API
bool IsCellNull(std::size_t rowInBatch, SQLUSMALLINT column)
const;
969 SQLSMALLINT sqlType {};
970 std::size_t elementWidth {};
971 std::vector<char> buffer;
972 std::vector<SQLLEN> indicators;
975 void ResetStatementState() noexcept;
980 [[nodiscard]]
char const* CheckedCell(std::
size_t rowInBatch,
983 char const* accessorName) const;
985 SqlStatement* m_stmt;
986 std::
size_t m_arrayDepth;
987 std::
size_t m_lastFetched = 0;
988 std::vector<BoundColumn> m_columns;
989 SQLULEN m_rowsFetched = 0;
990 std::vector<SQLUSMALLINT> m_rowStatus;
993struct [[nodiscard]] SqlSentinelIterator
997class [[nodiscard]] SqlVariantRowIterator
1000 explicit SqlVariantRowIterator(SqlSentinelIterator )
noexcept:
1006 explicit SqlVariantRowIterator(SqlResultCursor& cursor):
1007 _numResultColumns { static_cast<SQLUSMALLINT>(cursor.NumColumnsAffected()) },
1010 _row.reserve(_numResultColumns);
1014 SqlVariantRow& operator*() noexcept
1019 SqlVariantRow
const& operator*() const noexcept
1025 SqlVariantRowIterator& operator++()
1027 _end = !_cursor->FetchRow();
1031 for (
auto const i: std::views::iota(SQLUSMALLINT(1), SQLUSMALLINT(_numResultColumns + 1)))
1032 _row.emplace_back(_cursor->GetColumn<SqlVariant>(i));
1037 bool operator!=(SqlSentinelIterator )
const noexcept
1042 bool operator!=(SqlVariantRowIterator
const& )
const noexcept
1049 SQLUSMALLINT _numResultColumns = 0;
1050 SqlResultCursor* _cursor;
1054class [[nodiscard]] SqlVariantRowCursor
1057 explicit SqlVariantRowCursor(SqlResultCursor&& cursor):
1058 _resultCursor { std::move(cursor) }
1063 SqlVariantRowIterator begin()
1065 return SqlVariantRowIterator { _resultCursor };
1068 static SqlSentinelIterator end() noexcept
1070 return SqlSentinelIterator {};
1074 SqlResultCursor _resultCursor;
1111template <
typename T>
1123 _connection { &conn }
1132 _connection { &conn },
1133 _queryCustomizer { std::move(queryCustomizer) }
1140 using difference_type = bool;
1141 using value_type = T;
1143 iterator& operator++()
1147 _is_end = !_cursor->FetchRow();
1155 LIGHTWEIGHT_FORCE_INLINE value_type operator*()
1163 SQLUSMALLINT columnIndex = 0;
1165 if constexpr (RecordColumnMember<FieldType>)
1168 if constexpr (FieldWithStorage<FieldType>)
1169 value = _cursor->GetColumn<
typename FieldType::ValueType>(columnIndex);
1171 value = _cursor->GetColumn<FieldType>(columnIndex);
1178 LIGHTWEIGHT_FORCE_INLINE
constexpr bool operator!=(iterator
const& other)
const noexcept
1180 return _is_end != other._is_end;
1183 constexpr iterator(std::default_sentinel_t )
noexcept:
1185 _cursor { std::nullopt }
1189 explicit iterator(SqlConnection& conn):
1190 _stmt { std::make_unique<SqlStatement>(conn) },
1191 _cursor { std::nullopt }
1195 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
1200 void SetCursor(SqlResultCursor cursor)
noexcept
1202 _cursor.emplace(std::move(cursor));
1206 bool _is_end =
false;
1207 std::unique_ptr<SqlStatement> _stmt;
1208 std::optional<SqlResultCursor> _cursor;
1214 auto it = iterator { *_connection };
1215 auto& stmt = it.Statement();
1216 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().Build(_queryCustomizer).All());
1217 it.SetCursor(stmt.Execute());
1225 return iterator { std::default_sentinel };
1235inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsAlive() const noexcept
1237 return m_connection && m_connection->
IsAlive() && m_hStmt !=
nullptr;
1240inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsPrepared() const noexcept
1242 return !m_preparedQuery.empty();
1245inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection& SqlStatement::Connection() noexcept
1247 return *m_connection;
1250inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection const& SqlStatement::Connection() const noexcept
1252 return *m_connection;
1255inline LIGHTWEIGHT_FORCE_INLINE
SqlErrorInfo SqlStatement::LastError()
const
1257 return SqlErrorInfo::FromStatementHandle(m_hStmt);
1260inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
1265template <SqlQueryObject QueryObject>
1266inline void SqlStatement::AdoptProjectedFieldNames(QueryObject
const& queryObject)
1270 auto const names = queryObject.ProjectedFieldNames();
1271 m_projectedFieldNames.assign(names.begin(), names.end());
1272 m_projectionHasWildcard = queryObject.ProjectionHasWildcard();
1278 m_projectedFieldNames.clear();
1279 m_projectionHasWildcard =
false;
1283inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::Prepare(
SqlQueryObject auto const& queryObject) &
1287 AdoptProjectedFieldNames(queryObject);
1292 auto preparedStatement = std::move(*this).
Prepare(queryObject.ToSql());
1293 preparedStatement.AdoptProjectedFieldNames(queryObject);
1294 return preparedStatement;
1297inline LIGHTWEIGHT_FORCE_INLINE std::string
const& SqlStatement::PreparedQuery() const noexcept
1299 return m_preparedQuery;
1303template <SqlOutputColumnBinder... Args>
1304inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumns(Args*... args)
1306 if (ShouldRecordPrefetchBinding())
1311 ResetPrefetchBindings();
1313 ((++i, RecordPrefetchOutputColumn<Args>(i, args)), ...);
1317 RequireIndicators();
1320 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *
this))), ...);
1323template <
typename... Records>
1324 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
1325void SqlStatement::BindOutputColumnsToRecord(Records*... records)
1327 if (ShouldRecordPrefetchBinding())
1329 ResetPrefetchBindings();
1332 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1337 this->RecordPrefetchOutputColumn<FieldType>(i, &value);
1344 RequireIndicators();
1348 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1350 if constexpr (RecordColumnMember<FieldType>)
1353 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
1354 m_hStmt, i, &value, GetIndicatorForColumn(i), *
this));
1361template <SqlOutputColumnBinder T>
1362inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
1366 if (ShouldRecordPrefetchBinding())
1368 RecordPrefetchOutputColumn<T>(columnIndex, arg);
1372 RequireIndicators();
1374 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *
this));
1378template <SqlInputParameterBinder Arg>
1379inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg
const& arg)
1382 m_expectedParameterCount = (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)();
1383 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt,
static_cast<SQLUSMALLINT
>(columnIndex), arg, *
this));
1387template <SqlInputParameterBinder Arg,
typename ColumnName>
1388inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
1390 ColumnName&& columnNameHint)
1392 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
1393 BindInputParameter(columnIndex, arg);
1396template <SqlInputParameterBinder... Args>
1403 ZoneScopedN(
"SqlStatement::Execute");
1404 ZoneTextObject(m_preparedQuery);
1405 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1407 if (!(m_expectedParameterCount == (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)()
1408 &&
sizeof...(args) == 0)
1409 && !(m_expectedParameterCount ==
sizeof...(args)))
1410 throw std::invalid_argument {
"Invalid argument count" };
1414 SqlLogger::GetLogger().OnBindInputParameter({}, args),
1415 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *
this))),
1419 auto result = SQLExecute(m_hStmt);
1422 if (RetryStalePreparedStatement(result))
1423 result = SQLExecute(m_hStmt);
1425 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
1426 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
1428 ProcessPostExecuteCallbacks();
1433template <
typename T>
1434concept SqlNativeContiguousValueConcept =
1435 std::same_as<T, bool>
1436 || std::same_as<T, char>
1437 || std::same_as<T, unsigned char>
1438 || std::same_as<T, wchar_t>
1439 || std::same_as<T, std::int16_t>
1440 || std::same_as<T, std::uint16_t>
1441 || std::same_as<T, std::int32_t>
1442 || std::same_as<T, std::uint32_t>
1443 || std::same_as<T, std::int64_t>
1444 || std::same_as<T, std::uint64_t>
1445 || std::same_as<T, float>
1446 || std::same_as<T, double>
1447 || std::same_as<T, SqlDate>
1448 || std::same_as<T, SqlTime>
1449 || std::same_as<T, SqlDateTime>
1450 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
1452template <
typename FirstColumnBatch,
typename... MoreColumnBatches>
1453concept SqlNativeBatchable =
1454 std::ranges::contiguous_range<FirstColumnBatch>
1455 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
1456 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
1457 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
1464template <
typename V>
1470template <
typename V>
1476template <
typename V>
1489template <
typename V>
1498template <
typename V>
1500 requires(SQLHSTMT stmt, SQLUSMALLINT column, V
const* elem0, std::size_t n, SqlDataBinderCallback& cb) {
1501 { SqlDataBinder<V>::BatchRowWiseInputParameter(stmt, column, elem0, n, n, cb) } -> std::same_as<SQLRETURN>;
1504template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
1506 MoreColumnBatches
const&... moreColumnBatches)
1508 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
1509 "Must be a supported native contiguous element type.");
1511 ZoneScopedN(
"SqlStatement::ExecuteBatchNative");
1512 ZoneTextObject(m_preparedQuery);
1514 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1515 throw std::invalid_argument {
"Invalid number of columns" };
1517 auto const rowCount = std::ranges::size(firstColumnBatch);
1518 ZoneValue(rowCount);
1519 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1520 throw std::invalid_argument {
"Uneven number of rows" };
1522 size_t rowStart = 0;
1526 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1527 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
1528 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
1529 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1530 ClearBatchIndicators();
1531 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(firstColumnBatch))>>::
1532 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *
this));
1533 SQLUSMALLINT column = 1;
1534 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(moreColumnBatches))>>::
1535 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *
this)),
1539 RequireSuccess(SQLExecute(m_hStmt));
1541 ProcessPostExecuteCallbacks();
1547template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1548inline LIGHTWEIGHT_FORCE_INLINE
SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch
const& firstColumnBatch,
1549 MoreColumnBatches
const&... moreColumnBatches)
1553 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
1554 return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
1556 return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
1559template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1561 MoreColumnBatches
const&... moreColumnBatches)
1563 ZoneScopedN(
"SqlStatement::ExecuteBatchSoft");
1564 ZoneTextObject(m_preparedQuery);
1566 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1567 throw std::invalid_argument {
"Invalid number of columns" };
1569 auto const rowCount = std::ranges::size(firstColumnBatch);
1570 ZoneValue(rowCount);
1571 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1572 throw std::invalid_argument {
"Uneven number of rows" };
1574 for (
auto const rowIndex: std::views::iota(
size_t { 0 }, rowCount))
1577 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues
const&... columnsInRow) {
1578 SQLUSMALLINT column = 0;
1579 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
1581 LIGHTWEIGHT_STATS_SCOPE(::Lightweight::SqlStatisticsOperation::ExecuteBatch);
1582 RequireSuccess(SQLExecute(m_hStmt));
1584 ProcessPostExecuteCallbacks();
1587 std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch),
static_cast<std::ptrdiff_t
>(rowIndex))),
1589 *std::ranges::next(std::ranges::begin(moreColumnBatches),
static_cast<std::ptrdiff_t
>(rowIndex)))...));
1594template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1595 requires(
sizeof...(ColumnAccessors) >= 1
1596 && (std::invocable<ColumnAccessors
const&, std::ranges::range_value_t<Rows>
const&> && ...))
1599 ZoneScopedN(
"SqlStatement::ExecuteBatch(row-major)");
1600 ZoneTextObject(m_preparedQuery);
1602 using RowElem = std::ranges::range_value_t<Rows>;
1604 auto const rowCount = std::ranges::size(rows);
1608 if (m_expectedParameterCount !=
static_cast<SQLSMALLINT
>(
sizeof...(accessors)))
1609 throw std::invalid_argument {
"Invalid number of columns" };
1615 constexpr bool allColumnsRowBindable =
1617 constexpr bool allAccessorsReturnReference =
1618 (std::is_reference_v<std::invoke_result_t<ColumnAccessors const&, RowElem const&>> && ...);
1619 constexpr bool anyStridedIndicatorColumn =
1621 constexpr bool indicatorAlignmentSatisfied = (
sizeof(RowElem) %
alignof(SQLLEN)) == 0;
1623 if constexpr (allColumnsRowBindable && allAccessorsReturnReference
1624 && (!anyStridedIndicatorColumn || indicatorAlignmentSatisfied))
1626 auto const* rowData = std::ranges::data(rows);
1630 auto const accessorStrideMatchesRow = [&](
auto const& accessor)
noexcept ->
bool {
1631 auto const* first =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[0])));
1632 auto const* second =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[1])));
1633 return static_cast<std::size_t
>(second - first) ==
sizeof(RowElem);
1635 bool const rowStrideOk = rowCount < 2 || (accessorStrideMatchesRow(accessors) && ...);
1637 if (m_connection->SupportsNativeRowBatch() && rowStrideOk)
1638 return ExecuteBatchNativeRowWise(rows, accessors...);
1641 return ExecuteBatchSoftRowMajor(rows, accessors...);
1644template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1645SqlResultCursor SqlStatement::ExecuteBatchNativeRowWise(Rows
const& rows, ColumnAccessors
const&... accessors)
1647 ZoneScopedN(
"SqlStatement::ExecuteBatchNativeRowWise");
1648 ZoneTextObject(m_preparedQuery);
1650 using RowElem = std::ranges::range_value_t<Rows>;
1651 auto const rowCount = std::ranges::size(rows);
1652 ZoneValue(rowCount);
1653 auto const* rowData = std::ranges::data(rows);
1657 SQLULEN processedCount = rowCount;
1663 auto const restoreParameterBinding = detail::Finally([
this] {
1664 ResetParameterArrayBinding();
1665 ClearBatchIndicators();
1671 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1673 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)
sizeof(RowElem), 0));
1674 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
nullptr, 0));
1675 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1676 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &processedCount, 0));
1679 SQLUSMALLINT column = 0;
1680 auto const bindColumn = [&](
auto const& accessor) {
1682 using ValueType = std::remove_cvref_t<
decltype(accessor(rowData[0]))>;
1685 if constexpr (SqlHasRowWiseBatchBinder<ValueType>)
1686 RequireSuccess(SqlDataBinder<ValueType>::BatchRowWiseInputParameter(
1687 m_hStmt, column, std::addressof(accessor(rowData[0])),
sizeof(RowElem), rowCount, *
this));
1689 RequireSuccess(SqlDataBinder<ValueType>::InputParameter(m_hStmt, column, accessor(rowData[0]), *
this));
1691 (bindColumn(accessors), ...);
1697 auto const executeResult = SQLExecute(m_hStmt);
1698 RequireSuccessfulBatchExecute(executeResult, processedCount,
static_cast<SQLULEN
>(rowCount));
1699 ProcessPostExecuteCallbacks();
1701 return SqlResultCursor { *
this };
1704template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1705SqlResultCursor SqlStatement::ExecuteBatchSoftRowMajor(Rows
const& rows, ColumnAccessors
const&... accessors)
1707 ZoneScopedN(
"SqlStatement::ExecuteBatchSoftRowMajor");
1708 ZoneTextObject(m_preparedQuery);
1710 auto const* rowData = std::ranges::data(rows);
1711 auto const rowCount = std::ranges::size(rows);
1712 ZoneValue(rowCount);
1714 for (
auto const rowIndex: std::views::iota(std::size_t { 0 }, rowCount))
1716 auto const& row = rowData[rowIndex];
1717 SQLUSMALLINT column = 0;
1719 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(accessors(row))>>::InputParameter(
1720 m_hStmt, column, accessors(row), *
this))),
1725 RequireExecuteSucceededOrNoData(SQLExecute(m_hStmt));
1727 ProcessPostExecuteCallbacks();
1730 return SqlResultCursor { *
this };
1733template <
typename Value>
1734void SqlStatement::BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators)
1736 if constexpr (IsSqlFixedString<Value>)
1743 RequireSuccess(SQLBindCol(m_hStmt,
1746 (SQLPOINTER) SqlBasicStringOperations<Value>::Data(
static_cast<Value*
>(base0)),
1747 static_cast<SQLLEN
>(Value::Capacity) + 1,
1754 RequireSuccess(SqlDataBinder<Value>::OutputColumn(m_hStmt, column,
static_cast<Value*
>(base0), indicators, *
this));
1758template <
typename ValueType>
1759SQLLEN* SqlStatement::BindRowWiseOutputColumn(SQLUSMALLINT column,
void* base0, std::size_t rowStride, std::size_t depth)
1765 auto*
const indicatorBytes = ProvideBatchStagingBuffer(((depth - 1) * rowStride) +
sizeof(SQLLEN));
1766 auto*
const indicators =
reinterpret_cast<SQLLEN*
>(indicatorBytes);
1768 if constexpr (SqlIsStdOptional<ValueType>)
1770 using Inner = ValueType::value_type;
1771 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1774 for (
auto const i: std::views::iota(std::size_t { 0 }, depth))
1775 reinterpret_cast<ValueType*>(optBytes + (i * rowStride))->emplace();
1778 auto*
const contained0 =
reinterpret_cast<Inner*
>(optBytes + detail::OptionalValueOffset<Inner>());
1779 BindRowWiseValue<Inner>(column, contained0, indicators);
1783 BindRowWiseValue<ValueType>(column, base0, indicators);
1788template <
typename ValueType>
1789void SqlStatement::FinalizeRowWiseOutputColumn(
void* base0,
1790 std::size_t rowStride,
1791 std::size_t rowCount,
1792 SQLLEN
const* indicators)
noexcept
1794 auto const indicatorAt = [&](std::size_t i)
noexcept {
1795 return *
reinterpret_cast<SQLLEN const*
>(
reinterpret_cast<std::byte const*
>(indicators) + (i * rowStride));
1798 if constexpr (SqlIsStdOptional<ValueType>)
1800 using Inner = ValueType::value_type;
1801 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1802 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1804 auto*
const optional =
reinterpret_cast<ValueType*
>(optBytes + (i * rowStride));
1805 if (indicatorAt(i) == SQL_NULL_DATA)
1807 else if constexpr (IsSqlFixedString<Inner>)
1813 if (optional->has_value())
1814 SqlBasicStringOperations<Inner>::PostProcessOutputColumn(std::addressof(**optional), indicatorAt(i));
1819 else if constexpr (IsSqlFixedString<ValueType>)
1821 auto*
const base =
static_cast<std::byte*
>(base0);
1822 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1823 SqlBasicStringOperations<ValueType>::PostProcessOutputColumn(
1824 reinterpret_cast<ValueType*>(base + (i * rowStride)), indicatorAt(i));
1830template <
typename Record,
typename... ColumnAccessors>
1831void SqlStatement::FetchAllRowWise(std::vector<Record>& out, std::size_t arrayDepth, ColumnAccessors
const&... accessors)
1833 ZoneScopedN(
"SqlStatement::FetchAllRowWise");
1834 ZoneTextObject(m_preparedQuery);
1836 static_assert(
sizeof...(ColumnAccessors) >= 1,
"FetchAllRowWise requires at least one column accessor");
1837 constexpr std::size_t columnCount =
sizeof...(ColumnAccessors);
1844 auto const perRow =
sizeof(Record) * (1 + columnCount);
1847 arrayDepth = std::clamp(budgetDepth, minDepth, arrayDepth);
1850 std::vector<SQLUSMALLINT> rowStatus(arrayDepth);
1851 SQLULEN rowsFetched = 0;
1856 auto const restoreFetchState = detail::Finally([
this] {
1857 SQLFreeStmt(m_hStmt, SQL_UNBIND);
1860 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, 0);
1861 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
1862 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR,
nullptr, 0);
1863 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR,
nullptr, 0);
1865 ClearBatchIndicators();
1870 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)
sizeof(Record), 0));
1872 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arrayDepth, 0));
1873 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, rowStatus.data(), 0));
1874 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowsFetched, 0));
1879 std::size_t
const base = out.size();
1880 out.resize(base + arrayDepth);
1881 Record*
const row0 = out.data() + base;
1885 ClearBatchIndicators();
1886 std::array<SQLLEN*, columnCount> indicators {};
1887 SQLUSMALLINT column = 0;
1888 std::size_t bindIndex = 0;
1889 ((indicators[bindIndex++] = BindRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1890 ++column, std::addressof(accessors(*row0)),
sizeof(Record), arrayDepth)),
1894 auto const fetchResult = SQLFetchScroll(m_hStmt, SQL_FETCH_NEXT, 0);
1895 if (fetchResult == SQL_NO_DATA)
1902 if (!SQL_SUCCEEDED(fetchResult))
1903 RequireSuccess(fetchResult);
1905 auto const fetched =
static_cast<std::size_t
>(rowsFetched);
1907 LIGHTWEIGHT_STATS_ROWS(fetched,
true);
1909 std::size_t finalizeIndex = 0;
1910 (FinalizeRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1911 std::addressof(accessors(*row0)),
sizeof(Record), fetched, indicators[finalizeIndex++]),
1914 out.resize(base + fetched);
1915 if (fetched < arrayDepth)
1922template <SqlGetColumnNativeType T>
1923inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result)
const
1925 if (IsPrefetchActive())
1927 auto const& cursor = PrefetchCursorRef();
1928 auto const row = PrefetchRowInBlock();
1929 RequirePrefetchColumnInRange(cursor, column);
1930 if (cursor.IsCellNull(row, column))
1932 *result = ConvertCell<T>(cursor, row, column);
1935 SQLLEN indicator {};
1936 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *
this));
1937 return indicator != SQL_NULL_DATA;
1943 template <
typename T>
1944 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
1947 template <
typename T>
1948 struct IsSqlFixedStringSpec: std::false_type
1951 template <std::
size_t N,
typename Char, SqlFixedStringMode Mode>
1952 struct IsSqlFixedStringSpec<SqlFixedString<N, Char, Mode>>: std::true_type
1955 template <
typename T>
1956 concept SqlFixedStringCell = IsSqlFixedStringSpec<std::remove_cvref_t<T>>::value;
1959 template <
typename T>
1960 concept PlainStringCell =
1961 std::same_as<T, std::string> || std::same_as<T, std::u8string> || std::same_as<T, std::u16string>
1962 || std::same_as<T, std::u32string> || std::same_as<T, std::wstring>;
1965 template <
typename T>
1966 struct IsSqlNumericSpec: std::false_type
1969 template <std::
size_t Precision, std::
size_t Scale>
1970 struct IsSqlNumericSpec<SqlNumeric<Precision, Scale>>: std::true_type
1973 template <
typename T>
1974 concept SqlNumericCell = IsSqlNumericSpec<std::remove_cvref_t<T>>::value;
1977 [[nodiscard]]
inline std::u8string_view AsU8View(std::string
const& utf8)
noexcept
1979 return std::u8string_view {
reinterpret_cast<char8_t const*
>(utf8.data()), utf8.size() };
1991 template <SqlFixedStringMode Mode>
1992 inline void TrimFixedStringBytes(std::string& bytes)
noexcept
1994 auto const isTrailingTrimmable = [](
char c)
noexcept {
1997 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
1998 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
2002 while (!bytes.empty() && isTrailingTrimmable(bytes.back()))
2012 template <
typename Char>
2013 [[nodiscard]]
inline std::basic_string<Char> DecodeUtf8To(std::string
const& utf8)
2015 if constexpr (std::same_as<Char, char>)
2017 else if constexpr (std::same_as<Char, char8_t>)
2018 return std::u8string { AsU8View(utf8) };
2019 else if constexpr (std::same_as<Char, char16_t>)
2020 return ToUtf16(AsU8View(utf8));
2021 else if constexpr (std::same_as<Char, char32_t>)
2022 return ToUtf32<std::u32string>(AsU8View(utf8));
2030 template <
typename T>
2031 concept StringLikeCell = PlainStringCell<T> || SqlStringInterface<T>;
2036 template <
typename T>
2037 concept PrefetchConvertibleScalar =
2038 std::same_as<T, SqlVariant> || std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>
2039 || StringLikeCell<T> || std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>;
2041 template <
typename T>
2042 struct PrefetchConvertibleOptional: std::false_type
2045 template <
typename U>
2046 struct PrefetchConvertibleOptional<std::optional<U>>: std::bool_constant<PrefetchConvertibleScalar<U>>
2051 template <
typename T>
2052 concept PrefetchConvertible = PrefetchConvertibleScalar<T> || PrefetchConvertibleOptional<T>::value;
2057 template <
typename T>
2058 [[nodiscard]]
inline T ReadTemporalGuidCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2061 auto const boundType = cursor.ColumnBoundType(column);
2062 if constexpr (std::same_as<T, SqlDate>)
2063 return boundType == BoundType::Date ? cursor.GetDate(row, column).value_or(SqlDate {}) : SqlDate {};
2064 else if constexpr (std::same_as<T, SqlDateTime>)
2065 return boundType == BoundType::Timestamp ? cursor.GetTimestamp(row, column).value_or(SqlDateTime {})
2069 if (boundType == BoundType::Guid)
2070 return cursor.GetGuid(row, column).value_or(SqlGuid {});
2071 if (boundType == BoundType::Char || boundType == BoundType::WChar)
2072 return SqlGuid::TryParse(cursor.GetString(row, column).value_or(std::string {})).value_or(SqlGuid {});
2079 template <
typename T>
2080 [[nodiscard]]
inline T ReadNumericCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2083 switch (cursor.ColumnBoundType(column))
2085 case BoundType::Double:
2086 return T { cursor.GetF64(row, column).value_or(0.0) };
2087 case BoundType::Int64:
2088 return T {
static_cast<double>(cursor.GetI64(row, column).value_or(0)) };
2100 [[nodiscard]]
inline std::string RenderCellAsUtf8(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2102 switch (cursor.ColumnBoundType(column))
2106 return cursor.GetString(row, column).value_or(std::string {});
2108 return std::format(
"{}", cursor.GetI64(row, column).value_or(0));
2110 return std::format(
"{}", cursor.GetF64(row, column).value_or(0.0));
2112 return std::format(
"{}", cursor.GetDate(row, column).value_or(SqlDate {}));
2114 return std::format(
"{}", cursor.GetTimestamp(row, column).value_or(SqlDateTime {}));
2116 return std::format(
"{}", cursor.GetGuid(row, column).value_or(SqlGuid {}));
2118 return std::string {};
2126 template <
typename T>
2127 [[nodiscard]]
inline T ReadStringLikeCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2129 auto utf8 = RenderCellAsUtf8(cursor, row, column);
2130 if constexpr (SqlFixedStringCell<T>)
2131 TrimFixedStringBytes<T::PostRetrieveOperation>(utf8);
2132 return T { DecodeUtf8To<typename T::value_type>(utf8) };
2138 template <
typename T>
2139 [[nodiscard]]
inline T ReadArithmeticCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2142 switch (cursor.ColumnBoundType(column))
2144 case BoundType::Int64:
2145 return static_cast<T
>(cursor.GetI64(row, column).value_or(0));
2146 case BoundType::Double:
2147 return static_cast<T
>(cursor.GetF64(row, column).value_or(0.0));
2155template <
typename T>
2156inline T SqlStatement::ConvertCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
const
2161 if constexpr (std::same_as<T, SqlVariant>)
2162 return MakePrefetchVariantCell(cursor, row, column);
2163 else if constexpr (IsSpecializationOf<std::optional, T>)
2165 if (cursor.IsCellNull(row, column))
2166 return std::nullopt;
2167 return T { ConvertCell<typename T::value_type>(cursor, row, column) };
2169 else if constexpr (std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>)
2170 return detail::ReadTemporalGuidCell<T>(cursor, row, column);
2171 else if constexpr (detail::SqlNumericCell<T>)
2172 return detail::ReadNumericCell<T>(cursor, row, column);
2173 else if constexpr (detail::StringLikeCell<T>)
2174 return detail::ReadStringLikeCell<T>(cursor, row, column);
2175 else if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>)
2176 return detail::ReadArithmeticCell<T>(cursor, row, column);
2184template <SqlOutputColumnBinder T>
2185inline void SqlStatement::RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg)
2187 auto deferredBind = [
this, column, arg] {
2188 RequireIndicators();
2189 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, column, arg, GetIndicatorForColumn(column), *
this));
2191 if constexpr (detail::PrefetchConvertible<T>)
2193 RecordPrefetchColumn(
2195 [
this, column, arg] { *arg = ConvertCell<T>(PrefetchCursorRef(), PrefetchRowInBlock(), column); },
2196 std::move(deferredBind));
2202 RecordPrefetchColumn(column, {}, std::move(deferredBind));
2203 MarkPrefetchBindingUnsupported();
2207template <SqlGetColumnNativeType T>
2208inline T SqlStatement::GetColumn(SQLUSMALLINT column)
const
2210 if (IsPrefetchActive())
2212 auto const& cursor = PrefetchCursorRef();
2213 auto const row = PrefetchRowInBlock();
2214 RequirePrefetchColumnInRange(cursor, column);
2215 if constexpr (!detail::SqlNullableType<T>)
2216 if (cursor.IsCellNull(row, column))
2217 throw std::runtime_error {
"Column value is NULL" };
2218 return ConvertCell<T>(cursor, row, column);
2221 SQLLEN indicator {};
2226 ZoneScopedN(
"SqlStatement::ColumnGetData");
2227 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
2229 if constexpr (!detail::SqlNullableType<T>)
2230 if (indicator == SQL_NULL_DATA)
2231 throw std::runtime_error {
"Column value is NULL" };
2235template <SqlGetColumnNativeType T>
2236inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column)
const
2238 if (IsPrefetchActive())
2240 auto const& cursor = PrefetchCursorRef();
2241 auto const row = PrefetchRowInBlock();
2242 RequirePrefetchColumnInRange(cursor, column);
2243 if (cursor.IsCellNull(row, column))
2244 return std::nullopt;
2245 return ConvertCell<T>(cursor, row, column);
2248 SQLLEN indicator {};
2250 ZoneScopedN(
"SqlStatement::ColumnGetData");
2251 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
2253 if (indicator == SQL_NULL_DATA)
2254 return std::nullopt;
2255 return { std::move(result) };
2258template <SqlGetColumnNativeType T>
2259T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
2261 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
2265 std::source_location location)
2269 AdoptProjectedFieldNames(query);
2273template <
typename Callable>
2274 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
2277 ZoneScopedN(
"SqlStatement::MigrateDirect");
2279 callable(migration);
2280 auto const queries = migration.GetPlan().ToSql();
2281 ZoneValue(queries.size());
2287 auto const isCommentOnlyGuardScript = [](std::string_view script) {
2288 constexpr std::string_view marker =
"-- LIGHTWEIGHT_SQLITE_GUARD:";
2289 if (!script.starts_with(marker))
2291 auto const newline = script.find(
'\n');
2292 if (newline == std::string_view::npos)
2294 auto const body = script.substr(newline + 1);
2295 auto const bodyStart = body.find_first_not_of(
" \t\r\n");
2296 return bodyStart == std::string_view::npos || body.substr(bodyStart).starts_with(
"--");
2299 for (
auto const& query: queries)
2301 if (isCommentOnlyGuardScript(query))
2302 throw std::runtime_error(
2303 std::format(
"SqlStatement::MigrateDirect cannot apply this SQLite schema change directly because it "
2304 "requires a table rebuild (e.g. ALTER COLUMN or a foreign-key change). Apply it through "
2305 "MigrationManager::ApplyPendingMigrations, which runs the rebuild executor.\n Script: {}",
2307 [[maybe_unused]]
auto cursor =
ExecuteDirect(query, location);
2314template <
typename T>
2315 requires(!std::same_as<T, SqlVariant>)
2319 RequireSuccess(FetchRow());
2320 return GetNullableColumn<T>(1);
2323template <
typename T>
2324 requires(std::same_as<T, SqlVariant>)
2328 RequireSuccess(FetchRow());
2329 if (
auto result = GetNullableColumn<T>(1); result.has_value())
2334template <
typename T>
2335 requires(!std::same_as<T, SqlVariant>)
2338 return ExecuteDirectScalar<T>(query.ToSql(), location);
2341template <
typename T>
2342 requires(std::same_as<T, SqlVariant>)
2345 return ExecuteDirectScalar<T>(query.ToSql(), location);
2348inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::CloseCursor() noexcept
2353 ResetPrefetchState();
2367 auto const rc = SQLMoreResults(m_hStmt);
2368 if (rc == SQL_NO_DATA || !SQL_SUCCEEDED(rc))
2371 SQLFreeStmt(m_hStmt, SQL_CLOSE);
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...
static constexpr std::size_t MemoryBudgetBytes
static constexpr std::size_t MinArrayDepth
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 ...
@ Date
bound as SQL_C_TYPE_DATE into a SQL_DATE_STRUCT buffer
@ Char
bound as SQL_C_CHAR into a per-column byte buffer
@ Timestamp
bound as SQL_C_TYPE_TIMESTAMP into a SQL_TIMESTAMP_STRUCT buffer
@ WChar
bound as SQL_C_WCHAR (UTF-16) into a per-column byte buffer
@ Guid
bound as SQL_C_GUID into a 16-byte GUID buffer
@ Double
bound as SQL_C_DOUBLE into a double buffer
@ Int64
bound as SQL_C_SBIGINT into an int64 buffer
Represents a connection to a SQL database.
LIGHTWEIGHT_API bool IsAlive() const noexcept
Tests if the connection is still active.
LIGHTWEIGHT_API void ClearPreparedStatementCache() noexcept
Frees every pooled prepared statement handle, e.g. after DDL invalidated the cached query plans.
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
virtual void OnExecute(std::string_view const &query)=0
Invoked when a prepared query is executed.
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
virtual void OnFetchEnd()=0
Invoked when fetching is done.
virtual void OnExecuteBatch()=0
Invoked when a batch of queries is executed.
virtual void OnFetchRow()=0
Invoked when a row is fetched.
Query builder for building SQL migration queries.
A bounded LRU pool of already-prepared ODBC statement handles, owned by a SqlConnection.
API Entry point for building SQL queries.
LIGHTWEIGHT_FORCE_INLINE T GetColumn(std::string_view name) const
Retrieves the value of the named column for the currently selected row.
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)
T GetColumnOr(std::string_view name, T &&defaultValue) const
Retrieves the value of the named column, or defaultValue if it is NULL.
LIGHTWEIGHT_FORCE_INLINE std::optional< T > GetNullableColumn(std::string_view name) const
Retrieves the value of the named column, or std::nullopt if it is NULL.
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 void Prepare(std::string_view query) &
SqlPreparedStatementCaching PreparedStatementCaching() const noexcept
Whether this statement takes part in its connection's prepared-statement cache.
LIGHTWEIGHT_API SqlQueryBuilder QueryAs(std::string_view const &table, std::string_view const &tableAlias) const
Creates a new query builder for the given table with an alias, compatible with the SQL server being c...
void MigrateDirect(Callable const &callable, std::source_location location=std::source_location::current())
Executes an SQL migration query, as created b the callback.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor ExecuteBatch(FirstColumnBatch const &firstColumnBatch, MoreColumnBatches const &... moreColumnBatches)
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 SqlResultCursor ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
std::optional< T > ExecuteDirectScalar(std::string_view const &query, std::source_location location=std::source_location::current())
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).
Represents a query object that also knows the name of each column it projects.
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...
SqlPreparedStatementCaching
Whether a single SqlStatement takes part in its connection's prepared-statement cache.
@ ExecuteBatch
SQLExecute with a bound parameter array (batch insert/update).
@ Execute
SQLExecute of a prepared statement.
@ ExecuteDirect
SQLExecDirect of a one-shot statement.
@ Prepare
SQLPrepare of a statement.
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.
static std::optional< SqlGuid > TryParse(std::string_view const &text) noexcept
Parses a GUID from a string.
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.