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;
172 template <SqlInputParameterBinder Arg>
173 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg);
180 template <SqlInputParameterBinder Arg, typename ColumnName>
181 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint);
184 template <SqlInputParameterBinder Arg>
185 void BindInputParameter(SQLSMALLINT columnIndex, Arg const&& arg) = delete;
188 template <SqlInputParameterBinder Arg, typename ColumnName>
189 void BindInputParameter(SQLSMALLINT columnIndex, Arg const&& arg, ColumnName&& columnNameHint) = delete;
192 template <SqlInputParameterBinder... Args>
206 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
207 [[nodiscard]]
SqlResultCursor ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch,
208 MoreColumnBatches const&... moreColumnBatches);
218 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
219 [[nodiscard]]
SqlResultCursor ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch,
220 MoreColumnBatches const&... moreColumnBatches);
227 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
229 MoreColumnBatches const&... moreColumnBatches);
259 std::
size_t rowCount,
260 std::span<SQLUSMALLINT> paramStatus,
261 SQLULEN& paramsProcessed);
285 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
286 requires(sizeof...(ColumnAccessors) >= 1
287 && (std::invocable<ColumnAccessors const&, std::ranges::range_value_t<Rows> const&> && ...))
288 [[nodiscard]]
SqlResultCursor ExecuteBatch(Rows const& rows, ColumnAccessors const&... accessors);
292 ExecuteDirect(std::string_view const& query, std::source_location location = std::source_location::current());
296 std::source_location location = std::source_location::current());
310 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor ExecuteBatchFetch(std::string_view query, std::
size_t arrayDepth);
313 template <typename Callable>
315 void MigrateDirect(Callable const& callable, std::source_location location = std::source_location::current());
319 template <typename T>
321 [[nodiscard]] std::optional<T> ExecuteDirectScalar(std::string_view const& query,
322 std::source_location location = std::source_location::current());
325 template <typename T>
327 [[nodiscard]] T ExecuteDirectScalar(std::string_view const& query,
328 std::source_location location = std::source_location::current());
332 template <typename T>
334 [[nodiscard]] std::optional<T> ExecuteDirectScalar(
SqlQueryObject auto const& query,
335 std::source_location location = std::source_location::current());
338 template <typename T>
340 [[nodiscard]] T ExecuteDirectScalar(
SqlQueryObject auto const& query,
341 std::source_location location = std::source_location::current());
344 [[nodiscard]] LIGHTWEIGHT_API
size_t LastInsertId(std::string_view tableName);
356 LIGHTWEIGHT_API
void ConsumePendingResults(std::source_location location = std::source_location::current());
362 [[nodiscard]] LIGHTWEIGHT_API
size_t NumRowsAffected() const;
363 [[nodiscard]] LIGHTWEIGHT_API
size_t NumColumnsAffected() const;
364 [[nodiscard]] LIGHTWEIGHT_API
bool FetchRow();
365 [[nodiscard]] LIGHTWEIGHT_API std::expected<
bool,
SqlErrorInfo> TryFetchRow(
366 std::source_location location = std::source_location::current()) noexcept;
367 void CloseCursor() noexcept;
372 template <SqlOutputColumnBinder... Args>
373 void BindOutputColumns(Args*... args);
379 template <typename... Records>
380 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
381 void BindOutputColumnsToRecord(Records*... records);
387 template <SqlOutputColumnBinder T>
388 void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg);
390 template <SqlGetColumnNativeType T>
391 [[nodiscard]]
bool GetColumn(SQLUSMALLINT column, T* result) const;
393 template <SqlGetColumnNativeType T>
394 [[nodiscard]] T GetColumn(SQLUSMALLINT column) const;
398 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
399 [[nodiscard]]
SqlResultCursor ExecuteBatchNativeRowWise(Rows const& rows, ColumnAccessors const&... accessors);
403 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
404 [[nodiscard]]
SqlResultCursor ExecuteBatchSoftRowMajor(Rows const& rows, ColumnAccessors const&... accessors);
421 template <typename Record, typename... ColumnAccessors>
422 void FetchAllRowWise(std::vector<Record>& out, std::
size_t arrayDepth, ColumnAccessors const&... accessors);
427 template <typename ValueType>
428 [[nodiscard]] SQLLEN* BindRowWiseOutputColumn(SQLUSMALLINT column,
430 std::
size_t rowStride,
437 template <typename Value>
438 void BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators);
442 template <typename ValueType>
443 static
void FinalizeRowWiseOutputColumn(
void* base0,
444 std::
size_t rowStride,
445 std::
size_t rowCount,
446 SQLLEN const* indicators) noexcept;
448 template <SqlGetColumnNativeType T>
449 [[nodiscard]] std::optional<T> GetNullableColumn(SQLUSMALLINT column) const;
451 template <SqlGetColumnNativeType T>
452 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const;
466 [[nodiscard]] LIGHTWEIGHT_API SQLUSMALLINT ResolveColumnName(std::string_view name) const;
469 [[nodiscard]] LIGHTWEIGHT_API std::
string DescribeProjectedFieldNames() const;
471 LIGHTWEIGHT_API
void RequireSuccess(SQLRETURN error,
472 std::source_location sourceLocation = std::source_location::current()) const;
473 LIGHTWEIGHT_API
void PlanPostExecuteCallback(std::function<
void()>&& cb) override;
474 LIGHTWEIGHT_API
void PlanPostProcessOutputColumn(std::function<
void()>&& cb) override;
475 [[nodiscard]] LIGHTWEIGHT_API SqlServerType ServerType() const noexcept override;
476 [[nodiscard]] LIGHTWEIGHT_API std::
string const& DriverName() const noexcept override;
477 LIGHTWEIGHT_API
void ProcessPostExecuteCallbacks();
479 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicator() override;
480 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicators(
size_t rowCount) override;
481 LIGHTWEIGHT_API std::
byte* ProvideBatchStagingBuffer(std::
size_t byteCount) override;
486 [[nodiscard]] LIGHTWEIGHT_API std::optional<SQLSMALLINT> DescribeInputParameterType(
487 SQLUSMALLINT column) noexcept override;
488 LIGHTWEIGHT_API
void ClearBatchIndicators();
493 LIGHTWEIGHT_API
void BindRawBatchColumns(std::span<
SqlRawColumn const> columns,
494 std::
size_t rowCount,
495 std::source_location sourceLocation);
499 LIGHTWEIGHT_API
void ResetParameterArrayBinding() noexcept;
502 LIGHTWEIGHT_API
void RequireExecuteSucceededOrNoData(
503 SQLRETURN result, std::source_location sourceLocation = std::source_location::current()) const;
506 LIGHTWEIGHT_API
void RequireSuccessfulBatchExecute(
508 SQLULEN processedCount,
509 SQLULEN expectedCount,
510 std::source_location sourceLocation = std::source_location::current()) const;
527 [[nodiscard]] LIGHTWEIGHT_API
bool RetryStalePreparedStatement(SQLRETURN result);
529 LIGHTWEIGHT_API
void RequireIndicators();
530 LIGHTWEIGHT_API SQLLEN* GetIndicatorForColumn(SQLUSMALLINT column) noexcept;
539 [[nodiscard]] std::
size_t EffectivePrefetchDepth() const noexcept;
541 void ArmPrefetchOnFirstFetch() noexcept;
544 [[nodiscard]] std::expected<
bool,
SqlErrorInfo> FetchRowPrefetched() noexcept;
546 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrefetchActive() const noexcept;
548 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor const& PrefetchCursorRef() const noexcept;
550 [[nodiscard]] LIGHTWEIGHT_API std::
size_t PrefetchRowInBlock() const noexcept;
553 [[nodiscard]] LIGHTWEIGHT_API
bool ShouldRecordPrefetchBinding() const noexcept;
555 LIGHTWEIGHT_API
void ResetPrefetchBindings() noexcept;
558 LIGHTWEIGHT_API
void MarkPrefetchBindingUnsupported() noexcept;
567 LIGHTWEIGHT_API
void RecordPrefetchColumn(SQLUSMALLINT column,
568 std::function<
void()> scatter,
569 std::function<
void()> deferredBind);
571 LIGHTWEIGHT_API
void ResetPrefetchState() noexcept;
575 SQLUSMALLINT column) const;
577 template <typename T>
578 [[nodiscard]] T ConvertCell(
RowArrayCursor const& cursor, std::
size_t row, SQLUSMALLINT column) const;
583 LIGHTWEIGHT_API
void RequirePrefetchColumnInRange(
RowArrayCursor const& cursor, SQLUSMALLINT column) const;
587 template <SqlOutputColumnBinder T>
588 void RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg);
595 void AdoptProjectedFieldNames(QueryObject const& queryObject);
620 void PrepareOnHandle(std::
string queryText);
626 [[nodiscard]] SQLRETURN ExecDirectOnHandle(std::string_view query);
630 void RegisterHandleTextEntry() noexcept;
638 [[nodiscard]] std::vector<SQLSMALLINT> DescribeInputParameterTypes() noexcept;
642 bool ReleasePreparedHandle() noexcept;
650 void ReleasePreparedHandleForDirectExecution();
653 void EnsureStatementHandle();
657 std::unique_ptr<Data,
void (*)(Data*)> m_data;
660 std::string m_preparedQuery;
661 std::optional<SQLSMALLINT> m_numColumns;
664 std::vector<std::string> m_projectedFieldNames;
665 bool m_projectionHasWildcard =
false;
666 SQLSMALLINT m_expectedParameterCount {};
671 SQLSMALLINT m_preparedParameterCount {};
672 bool m_reusedPreparedQuery {
false };
674 SqlPreparedStatementCaching::Enabled
680 return m_preparedStatementCaching;
694 SqlResultCursor() =
delete;
695 SqlResultCursor(SqlResultCursor
const&) =
delete;
696 SqlResultCursor& operator=(SqlResultCursor
const&) =
delete;
700 m_stmt { other.m_stmt }
702 other.m_stmt =
nullptr;
710 m_stmt = other.m_stmt;
711 other.m_stmt =
nullptr;
720 m_stmt->CloseCursor();
728 return m_stmt->NumRowsAffected();
734 return m_stmt->NumColumnsAffected();
742 [[nodiscard]] LIGHTWEIGHT_API std::expected<SQLSMALLINT, SqlErrorInfo>
ColumnSqlType(SQLUSMALLINT column)
const;
745 LIGHTWEIGHT_FORCE_INLINE
void ConsumePendingResults(std::source_location location = std::source_location::current())
747 m_stmt->ConsumePendingResults(location);
753 template <SqlOutputColumnBinder... Args>
756 m_stmt->BindOutputColumns(args...);
760 template <SqlOutputColumnBinder T>
763 m_stmt->BindOutputColumn(columnIndex, arg);
767 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool FetchRow()
769 return m_stmt->FetchRow();
773 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::expected<bool, SqlErrorInfo>
TryFetchRow(
774 std::source_location location = std::source_location::current()) noexcept
776 return m_stmt->TryFetchRow(location);
780 template <
typename... Records>
781 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
784 m_stmt->BindOutputColumnsToRecord(records...);
793 template <
typename Record,
typename... ColumnAccessors>
795 std::size_t arrayDepth,
796 ColumnAccessors
const&... accessors)
798 m_stmt->FetchAllRowWise(out, arrayDepth, accessors...);
804 template <SqlGetColumnNativeType T>
805 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool GetColumn(SQLUSMALLINT column, T* result)
const
807 return m_stmt->GetColumn<T>(column, result);
811 template <SqlGetColumnNativeType T>
812 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(SQLUSMALLINT column)
const
814 return m_stmt->GetColumn<T>(column);
820 template <SqlGetColumnNativeType T>
821 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(SQLUSMALLINT column)
const
823 return m_stmt->GetNullableColumn<T>(column);
829 template <SqlGetColumnNativeType T>
830 [[nodiscard]] T
GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
832 return m_stmt->GetColumnOr(column, std::forward<T>(defaultValue));
848 template <SqlGetColumnNativeType T>
849 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(std::string_view name)
const
851 return m_stmt->GetColumn<T>(m_stmt->ResolveColumnName(name));
862 template <SqlGetColumnNativeType T>
863 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(std::string_view name)
const
865 return m_stmt->GetNullableColumn<T>(m_stmt->ResolveColumnName(name));
877 template <SqlGetColumnNativeType T>
878 [[nodiscard]] T
GetColumnOr(std::string_view name, T&& defaultValue)
const
880 return m_stmt->GetColumnOr(m_stmt->ResolveColumnName(name), std::forward<T>(defaultValue));
898 using std::runtime_error::runtime_error;
932 static constexpr std::size_t MaxCharColumnBytes = 8192;
938 static constexpr std::size_t MemoryBudgetBytes = 4 * 1024 * 1024;
942 static constexpr std::size_t MinArrayDepth = 16;
964 [[nodiscard]] LIGHTWEIGHT_API std::
size_t FetchArray();
967 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ColumnCount() const noexcept;
972 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ArrayDepth() const noexcept;
978 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::int64_t> GetI64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
984 [[nodiscard]] LIGHTWEIGHT_API std::optional<
double> GetF64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
997 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::
string> GetString(std::
size_t rowInBatch, SQLUSMALLINT column) const;
1003 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDate> GetDate(std::
size_t rowInBatch, SQLUSMALLINT column) const;
1010 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDateTime> GetTimestamp(std::
size_t rowInBatch, SQLUSMALLINT column) const;
1017 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlGuid> GetGuid(std::
size_t rowInBatch, SQLUSMALLINT column) const;
1043 [[nodiscard]] LIGHTWEIGHT_API SQLSMALLINT
ColumnSqlType(SQLUSMALLINT column)
const;
1049 [[nodiscard]] LIGHTWEIGHT_API
bool IsCellNull(std::size_t rowInBatch, SQLUSMALLINT column)
const;
1056 SQLSMALLINT sqlType {};
1057 std::size_t elementWidth {};
1058 std::vector<char> buffer;
1059 std::vector<SQLLEN> indicators;
1062 void ResetStatementState() noexcept;
1067 [[nodiscard]]
char const* CheckedCell(std::
size_t rowInBatch,
1068 SQLUSMALLINT column,
1070 char const* accessorName) const;
1072 SqlStatement* m_stmt;
1073 std::
size_t m_arrayDepth;
1074 std::
size_t m_lastFetched = 0;
1075 std::vector<BoundColumn> m_columns;
1076 SQLULEN m_rowsFetched = 0;
1077 std::vector<SQLUSMALLINT> m_rowStatus;
1080struct [[nodiscard]] SqlSentinelIterator
1084class [[nodiscard]] SqlVariantRowIterator
1087 explicit SqlVariantRowIterator(SqlSentinelIterator )
noexcept:
1093 explicit SqlVariantRowIterator(SqlResultCursor& cursor):
1094 _numResultColumns { static_cast<SQLUSMALLINT>(cursor.NumColumnsAffected()) },
1097 _row.reserve(_numResultColumns);
1101 SqlVariantRow& operator*() noexcept
1106 SqlVariantRow
const& operator*() const noexcept
1112 SqlVariantRowIterator& operator++()
1114 _end = !_cursor->FetchRow();
1118 for (
auto const i: std::views::iota(SQLUSMALLINT(1), SQLUSMALLINT(_numResultColumns + 1)))
1119 _row.emplace_back(_cursor->GetColumn<SqlVariant>(i));
1124 bool operator!=(SqlSentinelIterator )
const noexcept
1129 bool operator!=(SqlVariantRowIterator
const& )
const noexcept
1136 SQLUSMALLINT _numResultColumns = 0;
1137 SqlResultCursor* _cursor;
1141class [[nodiscard]] SqlVariantRowCursor
1144 explicit SqlVariantRowCursor(SqlResultCursor&& cursor):
1145 _resultCursor { std::move(cursor) }
1150 SqlVariantRowIterator begin()
1152 return SqlVariantRowIterator { _resultCursor };
1155 static SqlSentinelIterator end() noexcept
1157 return SqlSentinelIterator {};
1161 SqlResultCursor _resultCursor;
1198template <
typename T>
1210 _connection { &conn }
1219 _connection { &conn },
1220 _queryCustomizer { std::move(queryCustomizer) }
1227 using difference_type = bool;
1228 using value_type = T;
1230 iterator& operator++()
1234 _is_end = !_cursor->FetchRow();
1242 LIGHTWEIGHT_FORCE_INLINE value_type operator*()
1250 SQLUSMALLINT columnIndex = 0;
1252 if constexpr (RecordColumnMember<FieldType>)
1255 if constexpr (FieldWithStorage<FieldType>)
1256 value = _cursor->GetColumn<
typename FieldType::ValueType>(columnIndex);
1258 value = _cursor->GetColumn<FieldType>(columnIndex);
1265 LIGHTWEIGHT_FORCE_INLINE
constexpr bool operator!=(iterator
const& other)
const noexcept
1267 return _is_end != other._is_end;
1270 constexpr iterator(std::default_sentinel_t )
noexcept:
1272 _cursor { std::nullopt }
1276 explicit iterator(SqlConnection& conn):
1277 _stmt { std::make_unique<SqlStatement>(conn) },
1278 _cursor { std::nullopt }
1282 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
1287 void SetCursor(SqlResultCursor cursor)
noexcept
1289 _cursor.emplace(std::move(cursor));
1293 bool _is_end =
false;
1294 std::unique_ptr<SqlStatement> _stmt;
1295 std::optional<SqlResultCursor> _cursor;
1301 auto it = iterator { *_connection };
1302 auto& stmt = it.Statement();
1303 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().Build(_queryCustomizer).All());
1304 it.SetCursor(stmt.Execute());
1312 return iterator { std::default_sentinel };
1322inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsAlive() const noexcept
1324 return m_connection && m_connection->
IsAlive() && m_hStmt !=
nullptr;
1327inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsPrepared() const noexcept
1329 return !m_preparedQuery.empty();
1332inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection& SqlStatement::Connection() noexcept
1334 return *m_connection;
1337inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection const& SqlStatement::Connection() const noexcept
1339 return *m_connection;
1342inline LIGHTWEIGHT_FORCE_INLINE
SqlErrorInfo SqlStatement::LastError()
const
1344 return SqlErrorInfo::FromStatementHandle(m_hStmt);
1347inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
1352template <SqlQueryObject QueryObject>
1353inline void SqlStatement::AdoptProjectedFieldNames(QueryObject
const& queryObject)
1357 auto const names = queryObject.ProjectedFieldNames();
1358 m_projectedFieldNames.assign(names.begin(), names.end());
1359 m_projectionHasWildcard = queryObject.ProjectionHasWildcard();
1365 m_projectedFieldNames.clear();
1366 m_projectionHasWildcard =
false;
1370inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::Prepare(
SqlQueryObject auto const& queryObject) &
1374 AdoptProjectedFieldNames(queryObject);
1379 auto preparedStatement = std::move(*this).
Prepare(queryObject.ToSql());
1380 preparedStatement.AdoptProjectedFieldNames(queryObject);
1381 return preparedStatement;
1384inline LIGHTWEIGHT_FORCE_INLINE std::string
const& SqlStatement::PreparedQuery() const noexcept
1386 return m_preparedQuery;
1390template <SqlOutputColumnBinder... Args>
1391inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumns(Args*... args)
1393 if (ShouldRecordPrefetchBinding())
1398 ResetPrefetchBindings();
1400 ((++i, RecordPrefetchOutputColumn<Args>(i, args)), ...);
1404 RequireIndicators();
1407 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *
this))), ...);
1410template <
typename... Records>
1411 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
1412void SqlStatement::BindOutputColumnsToRecord(Records*... records)
1414 if (ShouldRecordPrefetchBinding())
1416 ResetPrefetchBindings();
1419 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1424 this->RecordPrefetchOutputColumn<FieldType>(i, &value);
1431 RequireIndicators();
1435 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1437 if constexpr (RecordColumnMember<FieldType>)
1440 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
1441 m_hStmt, i, &value, GetIndicatorForColumn(i), *
this));
1448template <SqlOutputColumnBinder T>
1449inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
1453 if (ShouldRecordPrefetchBinding())
1455 RecordPrefetchOutputColumn<T>(columnIndex, arg);
1459 RequireIndicators();
1461 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *
this));
1465template <SqlInputParameterBinder Arg>
1466inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg
const& arg)
1469 m_expectedParameterCount = (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)();
1470 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt,
static_cast<SQLUSMALLINT
>(columnIndex), arg, *
this));
1474template <SqlInputParameterBinder Arg,
typename ColumnName>
1475inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
1477 ColumnName&& columnNameHint)
1479 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
1480 BindInputParameter(columnIndex, arg);
1483template <SqlInputParameterBinder... Args>
1490 ZoneScopedN(
"SqlStatement::Execute");
1491 ZoneTextObject(m_preparedQuery);
1492 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1494 if (!(m_expectedParameterCount == (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)()
1495 &&
sizeof...(args) == 0)
1496 && !(m_expectedParameterCount ==
sizeof...(args)))
1497 throw std::invalid_argument {
"Invalid argument count" };
1501 SqlLogger::GetLogger().OnBindInputParameter({}, args),
1502 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *
this))),
1506 auto result = SQLExecute(m_hStmt);
1509 if (RetryStalePreparedStatement(result))
1510 result = SQLExecute(m_hStmt);
1512 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
1513 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
1515 ProcessPostExecuteCallbacks();
1520template <
typename T>
1521concept SqlNativeContiguousValueConcept =
1522 std::same_as<T, bool>
1523 || std::same_as<T, char>
1524 || std::same_as<T, unsigned char>
1525 || std::same_as<T, wchar_t>
1526 || std::same_as<T, std::int16_t>
1527 || std::same_as<T, std::uint16_t>
1528 || std::same_as<T, std::int32_t>
1529 || std::same_as<T, std::uint32_t>
1530 || std::same_as<T, std::int64_t>
1531 || std::same_as<T, std::uint64_t>
1532 || std::same_as<T, float>
1533 || std::same_as<T, double>
1534 || std::same_as<T, SqlDate>
1535 || std::same_as<T, SqlTime>
1536 || std::same_as<T, SqlDateTime>
1537 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
1539template <
typename FirstColumnBatch,
typename... MoreColumnBatches>
1540concept SqlNativeBatchable =
1541 std::ranges::contiguous_range<FirstColumnBatch>
1542 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
1543 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
1544 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
1551template <
typename V>
1557template <
typename V>
1563template <
typename V>
1576template <
typename V>
1585template <
typename V>
1587 requires(SQLHSTMT stmt, SQLUSMALLINT column, V
const* elem0, std::size_t n, SqlDataBinderCallback& cb) {
1588 { SqlDataBinder<V>::BatchRowWiseInputParameter(stmt, column, elem0, n, n, cb) } -> std::same_as<SQLRETURN>;
1591template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
1593 MoreColumnBatches
const&... moreColumnBatches)
1595 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
1596 "Must be a supported native contiguous element type.");
1598 ZoneScopedN(
"SqlStatement::ExecuteBatchNative");
1599 ZoneTextObject(m_preparedQuery);
1601 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1602 throw std::invalid_argument {
"Invalid number of columns" };
1604 auto const rowCount = std::ranges::size(firstColumnBatch);
1605 ZoneValue(rowCount);
1606 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1607 throw std::invalid_argument {
"Uneven number of rows" };
1609 size_t rowStart = 0;
1613 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1614 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
1615 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
1616 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1617 ClearBatchIndicators();
1618 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(firstColumnBatch))>>::
1619 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *
this));
1620 SQLUSMALLINT column = 1;
1621 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(moreColumnBatches))>>::
1622 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *
this)),
1626 RequireSuccess(SQLExecute(m_hStmt));
1628 ProcessPostExecuteCallbacks();
1634template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1635inline LIGHTWEIGHT_FORCE_INLINE
SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch
const& firstColumnBatch,
1636 MoreColumnBatches
const&... moreColumnBatches)
1640 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
1641 return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
1643 return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
1646template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1648 MoreColumnBatches
const&... moreColumnBatches)
1650 ZoneScopedN(
"SqlStatement::ExecuteBatchSoft");
1651 ZoneTextObject(m_preparedQuery);
1653 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1654 throw std::invalid_argument {
"Invalid number of columns" };
1656 auto const rowCount = std::ranges::size(firstColumnBatch);
1657 ZoneValue(rowCount);
1658 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1659 throw std::invalid_argument {
"Uneven number of rows" };
1661 for (
auto const rowIndex: std::views::iota(
size_t { 0 }, rowCount))
1664 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues
const&... columnsInRow) {
1665 SQLUSMALLINT column = 0;
1666 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
1668 LIGHTWEIGHT_STATS_SCOPE(::Lightweight::SqlStatisticsOperation::ExecuteBatch);
1669 RequireSuccess(SQLExecute(m_hStmt));
1671 ProcessPostExecuteCallbacks();
1674 std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch),
static_cast<std::ptrdiff_t
>(rowIndex))),
1676 *std::ranges::next(std::ranges::begin(moreColumnBatches),
static_cast<std::ptrdiff_t
>(rowIndex)))...));
1681template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1682 requires(
sizeof...(ColumnAccessors) >= 1
1683 && (std::invocable<ColumnAccessors
const&, std::ranges::range_value_t<Rows>
const&> && ...))
1686 ZoneScopedN(
"SqlStatement::ExecuteBatch(row-major)");
1687 ZoneTextObject(m_preparedQuery);
1689 using RowElem = std::ranges::range_value_t<Rows>;
1691 auto const rowCount = std::ranges::size(rows);
1695 if (m_expectedParameterCount !=
static_cast<SQLSMALLINT
>(
sizeof...(accessors)))
1696 throw std::invalid_argument {
"Invalid number of columns" };
1702 constexpr bool allColumnsRowBindable =
1704 constexpr bool allAccessorsReturnReference =
1705 (std::is_reference_v<std::invoke_result_t<ColumnAccessors const&, RowElem const&>> && ...);
1706 constexpr bool anyStridedIndicatorColumn =
1708 constexpr bool indicatorAlignmentSatisfied = (
sizeof(RowElem) %
alignof(SQLLEN)) == 0;
1710 if constexpr (allColumnsRowBindable && allAccessorsReturnReference
1711 && (!anyStridedIndicatorColumn || indicatorAlignmentSatisfied))
1713 auto const* rowData = std::ranges::data(rows);
1717 auto const accessorStrideMatchesRow = [&](
auto const& accessor)
noexcept ->
bool {
1718 auto const* first =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[0])));
1719 auto const* second =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[1])));
1720 return static_cast<std::size_t
>(second - first) ==
sizeof(RowElem);
1722 bool const rowStrideOk = rowCount < 2 || (accessorStrideMatchesRow(accessors) && ...);
1724 if (m_connection->SupportsNativeRowBatch() && rowStrideOk)
1725 return ExecuteBatchNativeRowWise(rows, accessors...);
1728 return ExecuteBatchSoftRowMajor(rows, accessors...);
1731template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1732SqlResultCursor SqlStatement::ExecuteBatchNativeRowWise(Rows
const& rows, ColumnAccessors
const&... accessors)
1734 ZoneScopedN(
"SqlStatement::ExecuteBatchNativeRowWise");
1735 ZoneTextObject(m_preparedQuery);
1737 using RowElem = std::ranges::range_value_t<Rows>;
1738 auto const rowCount = std::ranges::size(rows);
1739 ZoneValue(rowCount);
1740 auto const* rowData = std::ranges::data(rows);
1744 SQLULEN processedCount = rowCount;
1750 auto const restoreParameterBinding = detail::Finally([
this] {
1751 ResetParameterArrayBinding();
1752 ClearBatchIndicators();
1758 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1760 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)
sizeof(RowElem), 0));
1761 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
nullptr, 0));
1762 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1763 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &processedCount, 0));
1766 SQLUSMALLINT column = 0;
1767 auto const bindColumn = [&](
auto const& accessor) {
1769 using ValueType = std::remove_cvref_t<
decltype(accessor(rowData[0]))>;
1772 if constexpr (SqlHasRowWiseBatchBinder<ValueType>)
1773 RequireSuccess(SqlDataBinder<ValueType>::BatchRowWiseInputParameter(
1774 m_hStmt, column, std::addressof(accessor(rowData[0])),
sizeof(RowElem), rowCount, *
this));
1776 RequireSuccess(SqlDataBinder<ValueType>::InputParameter(m_hStmt, column, accessor(rowData[0]), *
this));
1778 (bindColumn(accessors), ...);
1784 auto const executeResult = SQLExecute(m_hStmt);
1785 RequireSuccessfulBatchExecute(executeResult, processedCount,
static_cast<SQLULEN
>(rowCount));
1786 ProcessPostExecuteCallbacks();
1788 return SqlResultCursor { *
this };
1791template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1792SqlResultCursor SqlStatement::ExecuteBatchSoftRowMajor(Rows
const& rows, ColumnAccessors
const&... accessors)
1794 ZoneScopedN(
"SqlStatement::ExecuteBatchSoftRowMajor");
1795 ZoneTextObject(m_preparedQuery);
1797 auto const* rowData = std::ranges::data(rows);
1798 auto const rowCount = std::ranges::size(rows);
1799 ZoneValue(rowCount);
1801 for (
auto const rowIndex: std::views::iota(std::size_t { 0 }, rowCount))
1803 auto const& row = rowData[rowIndex];
1804 SQLUSMALLINT column = 0;
1806 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(accessors(row))>>::InputParameter(
1807 m_hStmt, column, accessors(row), *
this))),
1812 RequireExecuteSucceededOrNoData(SQLExecute(m_hStmt));
1814 ProcessPostExecuteCallbacks();
1817 return SqlResultCursor { *
this };
1820template <
typename Value>
1821void SqlStatement::BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators)
1823 if constexpr (IsSqlFixedString<Value>)
1830 RequireSuccess(SQLBindCol(m_hStmt,
1833 (SQLPOINTER) SqlBasicStringOperations<Value>::Data(
static_cast<Value*
>(base0)),
1834 static_cast<SQLLEN
>(Value::Capacity) + 1,
1841 RequireSuccess(SqlDataBinder<Value>::OutputColumn(m_hStmt, column,
static_cast<Value*
>(base0), indicators, *
this));
1845template <
typename ValueType>
1846SQLLEN* SqlStatement::BindRowWiseOutputColumn(SQLUSMALLINT column,
void* base0, std::size_t rowStride, std::size_t depth)
1852 auto*
const indicatorBytes = ProvideBatchStagingBuffer(((depth - 1) * rowStride) +
sizeof(SQLLEN));
1853 auto*
const indicators =
reinterpret_cast<SQLLEN*
>(indicatorBytes);
1855 if constexpr (SqlIsStdOptional<ValueType>)
1857 using Inner = ValueType::value_type;
1858 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1861 for (
auto const i: std::views::iota(std::size_t { 0 }, depth))
1862 reinterpret_cast<ValueType*>(optBytes + (i * rowStride))->emplace();
1865 auto*
const contained0 =
reinterpret_cast<Inner*
>(optBytes + detail::OptionalValueOffset<Inner>());
1866 BindRowWiseValue<Inner>(column, contained0, indicators);
1870 BindRowWiseValue<ValueType>(column, base0, indicators);
1875template <
typename ValueType>
1876void SqlStatement::FinalizeRowWiseOutputColumn(
void* base0,
1877 std::size_t rowStride,
1878 std::size_t rowCount,
1879 SQLLEN
const* indicators)
noexcept
1881 auto const indicatorAt = [&](std::size_t i)
noexcept {
1882 return *
reinterpret_cast<SQLLEN const*
>(
reinterpret_cast<std::byte const*
>(indicators) + (i * rowStride));
1885 if constexpr (SqlIsStdOptional<ValueType>)
1887 using Inner = ValueType::value_type;
1888 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1889 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1891 auto*
const optional =
reinterpret_cast<ValueType*
>(optBytes + (i * rowStride));
1892 if (indicatorAt(i) == SQL_NULL_DATA)
1894 else if constexpr (IsSqlFixedString<Inner>)
1900 if (optional->has_value())
1901 SqlBasicStringOperations<Inner>::PostProcessOutputColumn(std::addressof(**optional), indicatorAt(i));
1906 else if constexpr (IsSqlFixedString<ValueType>)
1908 auto*
const base =
static_cast<std::byte*
>(base0);
1909 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1910 SqlBasicStringOperations<ValueType>::PostProcessOutputColumn(
1911 reinterpret_cast<ValueType*>(base + (i * rowStride)), indicatorAt(i));
1917template <
typename Record,
typename... ColumnAccessors>
1918void SqlStatement::FetchAllRowWise(std::vector<Record>& out, std::size_t arrayDepth, ColumnAccessors
const&... accessors)
1920 ZoneScopedN(
"SqlStatement::FetchAllRowWise");
1921 ZoneTextObject(m_preparedQuery);
1923 static_assert(
sizeof...(ColumnAccessors) >= 1,
"FetchAllRowWise requires at least one column accessor");
1924 constexpr std::size_t columnCount =
sizeof...(ColumnAccessors);
1931 auto const perRow =
sizeof(Record) * (1 + columnCount);
1934 arrayDepth = std::clamp(budgetDepth, minDepth, arrayDepth);
1937 std::vector<SQLUSMALLINT> rowStatus(arrayDepth);
1938 SQLULEN rowsFetched = 0;
1943 auto const restoreFetchState = detail::Finally([
this] {
1944 SQLFreeStmt(m_hStmt, SQL_UNBIND);
1947 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, 0);
1948 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
1949 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR,
nullptr, 0);
1950 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR,
nullptr, 0);
1952 ClearBatchIndicators();
1957 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)
sizeof(Record), 0));
1959 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arrayDepth, 0));
1960 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, rowStatus.data(), 0));
1961 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowsFetched, 0));
1966 std::size_t
const base = out.size();
1967 out.resize(base + arrayDepth);
1968 Record*
const row0 = out.data() + base;
1972 ClearBatchIndicators();
1973 std::array<SQLLEN*, columnCount> indicators {};
1974 SQLUSMALLINT column = 0;
1975 std::size_t bindIndex = 0;
1976 ((indicators[bindIndex++] = BindRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1977 ++column, std::addressof(accessors(*row0)),
sizeof(Record), arrayDepth)),
1981 auto const fetchResult = SQLFetchScroll(m_hStmt, SQL_FETCH_NEXT, 0);
1982 if (fetchResult == SQL_NO_DATA)
1989 if (!SQL_SUCCEEDED(fetchResult))
1990 RequireSuccess(fetchResult);
1992 auto const fetched =
static_cast<std::size_t
>(rowsFetched);
1994 LIGHTWEIGHT_STATS_ROWS(fetched,
true);
1996 std::size_t finalizeIndex = 0;
1997 (FinalizeRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1998 std::addressof(accessors(*row0)),
sizeof(Record), fetched, indicators[finalizeIndex++]),
2001 out.resize(base + fetched);
2002 if (fetched < arrayDepth)
2009template <SqlGetColumnNativeType T>
2010inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result)
const
2012 if (IsPrefetchActive())
2014 auto const& cursor = PrefetchCursorRef();
2015 auto const row = PrefetchRowInBlock();
2016 RequirePrefetchColumnInRange(cursor, column);
2017 if (cursor.IsCellNull(row, column))
2019 *result = ConvertCell<T>(cursor, row, column);
2022 SQLLEN indicator {};
2023 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *
this));
2024 return indicator != SQL_NULL_DATA;
2030 template <
typename T>
2031 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
2034 template <
typename T>
2035 struct IsSqlFixedStringSpec: std::false_type
2038 template <std::
size_t N,
typename Char, SqlFixedStringMode Mode>
2039 struct IsSqlFixedStringSpec<SqlFixedString<N, Char, Mode>>: std::true_type
2042 template <
typename T>
2043 concept SqlFixedStringCell = IsSqlFixedStringSpec<std::remove_cvref_t<T>>::value;
2046 template <
typename T>
2047 concept PlainStringCell =
2048 std::same_as<T, std::string> || std::same_as<T, std::u8string> || std::same_as<T, std::u16string>
2049 || std::same_as<T, std::u32string> || std::same_as<T, std::wstring>;
2052 template <
typename T>
2053 struct IsSqlNumericSpec: std::false_type
2056 template <std::
size_t Precision, std::
size_t Scale>
2057 struct IsSqlNumericSpec<SqlNumeric<Precision, Scale>>: std::true_type
2060 template <
typename T>
2061 concept SqlNumericCell = IsSqlNumericSpec<std::remove_cvref_t<T>>::value;
2064 [[nodiscard]]
inline std::u8string_view AsU8View(std::string
const& utf8)
noexcept
2066 return std::u8string_view {
reinterpret_cast<char8_t const*
>(utf8.data()), utf8.size() };
2078 template <SqlFixedStringMode Mode>
2079 inline void TrimFixedStringBytes(std::string& bytes)
noexcept
2081 auto const isTrailingTrimmable = [](
char c)
noexcept {
2084 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
2085 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
2089 while (!bytes.empty() && isTrailingTrimmable(bytes.back()))
2099 template <
typename Char>
2100 [[nodiscard]]
inline std::basic_string<Char> DecodeUtf8To(std::string
const& utf8)
2102 if constexpr (std::same_as<Char, char>)
2104 else if constexpr (std::same_as<Char, char8_t>)
2105 return std::u8string { AsU8View(utf8) };
2106 else if constexpr (std::same_as<Char, char16_t>)
2107 return ToUtf16(AsU8View(utf8));
2108 else if constexpr (std::same_as<Char, char32_t>)
2109 return ToUtf32<std::u32string>(AsU8View(utf8));
2117 template <
typename T>
2118 concept StringLikeCell = PlainStringCell<T> || SqlStringInterface<T>;
2123 template <
typename T>
2124 concept PrefetchConvertibleScalar =
2125 std::same_as<T, SqlVariant> || std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>
2126 || StringLikeCell<T> || std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>;
2128 template <
typename T>
2129 struct PrefetchConvertibleOptional: std::false_type
2132 template <
typename U>
2133 struct PrefetchConvertibleOptional<std::optional<U>>: std::bool_constant<PrefetchConvertibleScalar<U>>
2138 template <
typename T>
2139 concept PrefetchConvertible = PrefetchConvertibleScalar<T> || PrefetchConvertibleOptional<T>::value;
2144 template <
typename T>
2145 [[nodiscard]]
inline T ReadTemporalGuidCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2148 auto const boundType = cursor.ColumnBoundType(column);
2149 if constexpr (std::same_as<T, SqlDate>)
2150 return boundType == BoundType::Date ? cursor.GetDate(row, column).value_or(SqlDate {}) : SqlDate {};
2151 else if constexpr (std::same_as<T, SqlDateTime>)
2152 return boundType == BoundType::Timestamp ? cursor.GetTimestamp(row, column).value_or(SqlDateTime {})
2156 if (boundType == BoundType::Guid)
2157 return cursor.GetGuid(row, column).value_or(SqlGuid {});
2158 if (boundType == BoundType::Char || boundType == BoundType::WChar)
2159 return SqlGuid::TryParse(cursor.GetString(row, column).value_or(std::string {})).value_or(SqlGuid {});
2166 template <
typename T>
2167 [[nodiscard]]
inline T ReadNumericCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2170 switch (cursor.ColumnBoundType(column))
2172 case BoundType::Double:
2173 return T { cursor.GetF64(row, column).value_or(0.0) };
2174 case BoundType::Int64:
2175 return T {
static_cast<double>(cursor.GetI64(row, column).value_or(0)) };
2187 [[nodiscard]]
inline std::string RenderCellAsUtf8(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2189 switch (cursor.ColumnBoundType(column))
2193 return cursor.GetString(row, column).value_or(std::string {});
2195 return std::format(
"{}", cursor.GetI64(row, column).value_or(0));
2197 return std::format(
"{}", cursor.GetF64(row, column).value_or(0.0));
2199 return std::format(
"{}", cursor.GetDate(row, column).value_or(SqlDate {}));
2201 return std::format(
"{}", cursor.GetTimestamp(row, column).value_or(SqlDateTime {}));
2203 return std::format(
"{}", cursor.GetGuid(row, column).value_or(SqlGuid {}));
2205 return std::string {};
2213 template <
typename T>
2214 [[nodiscard]]
inline T ReadStringLikeCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2216 auto utf8 = RenderCellAsUtf8(cursor, row, column);
2217 if constexpr (SqlFixedStringCell<T>)
2218 TrimFixedStringBytes<T::PostRetrieveOperation>(utf8);
2219 return T { DecodeUtf8To<typename T::value_type>(utf8) };
2225 template <
typename T>
2226 [[nodiscard]]
inline T ReadArithmeticCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
2229 switch (cursor.ColumnBoundType(column))
2231 case BoundType::Int64:
2232 return static_cast<T
>(cursor.GetI64(row, column).value_or(0));
2233 case BoundType::Double:
2234 return static_cast<T
>(cursor.GetF64(row, column).value_or(0.0));
2242template <
typename T>
2243inline T SqlStatement::ConvertCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
const
2248 if constexpr (std::same_as<T, SqlVariant>)
2249 return MakePrefetchVariantCell(cursor, row, column);
2250 else if constexpr (IsSpecializationOf<std::optional, T>)
2252 if (cursor.IsCellNull(row, column))
2253 return std::nullopt;
2254 return T { ConvertCell<typename T::value_type>(cursor, row, column) };
2256 else if constexpr (std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>)
2257 return detail::ReadTemporalGuidCell<T>(cursor, row, column);
2258 else if constexpr (detail::SqlNumericCell<T>)
2259 return detail::ReadNumericCell<T>(cursor, row, column);
2260 else if constexpr (detail::StringLikeCell<T>)
2261 return detail::ReadStringLikeCell<T>(cursor, row, column);
2262 else if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>)
2263 return detail::ReadArithmeticCell<T>(cursor, row, column);
2271template <SqlOutputColumnBinder T>
2272inline void SqlStatement::RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg)
2274 auto deferredBind = [
this, column, arg] {
2275 RequireIndicators();
2276 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, column, arg, GetIndicatorForColumn(column), *
this));
2278 if constexpr (detail::PrefetchConvertible<T>)
2280 RecordPrefetchColumn(
2282 [
this, column, arg] { *arg = ConvertCell<T>(PrefetchCursorRef(), PrefetchRowInBlock(), column); },
2283 std::move(deferredBind));
2289 RecordPrefetchColumn(column, {}, std::move(deferredBind));
2290 MarkPrefetchBindingUnsupported();
2294template <SqlGetColumnNativeType T>
2295inline T SqlStatement::GetColumn(SQLUSMALLINT column)
const
2297 if (IsPrefetchActive())
2299 auto const& cursor = PrefetchCursorRef();
2300 auto const row = PrefetchRowInBlock();
2301 RequirePrefetchColumnInRange(cursor, column);
2302 if constexpr (!detail::SqlNullableType<T>)
2303 if (cursor.IsCellNull(row, column))
2304 throw std::runtime_error {
"Column value is NULL" };
2305 return ConvertCell<T>(cursor, row, column);
2308 SQLLEN indicator {};
2313 ZoneScopedN(
"SqlStatement::ColumnGetData");
2314 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
2316 if constexpr (!detail::SqlNullableType<T>)
2317 if (indicator == SQL_NULL_DATA)
2318 throw std::runtime_error {
"Column value is NULL" };
2322template <SqlGetColumnNativeType T>
2323inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column)
const
2325 if (IsPrefetchActive())
2327 auto const& cursor = PrefetchCursorRef();
2328 auto const row = PrefetchRowInBlock();
2329 RequirePrefetchColumnInRange(cursor, column);
2330 if (cursor.IsCellNull(row, column))
2331 return std::nullopt;
2332 return ConvertCell<T>(cursor, row, column);
2335 SQLLEN indicator {};
2337 ZoneScopedN(
"SqlStatement::ColumnGetData");
2338 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
2340 if (indicator == SQL_NULL_DATA)
2341 return std::nullopt;
2342 return { std::move(result) };
2345template <SqlGetColumnNativeType T>
2346T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
2348 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
2352 std::source_location location)
2356 AdoptProjectedFieldNames(query);
2360template <
typename Callable>
2361 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
2364 ZoneScopedN(
"SqlStatement::MigrateDirect");
2366 callable(migration);
2367 auto const queries = migration.GetPlan().ToSql();
2368 ZoneValue(queries.size());
2374 auto const isCommentOnlyGuardScript = [](std::string_view script) {
2375 constexpr std::string_view marker =
"-- LIGHTWEIGHT_SQLITE_GUARD:";
2376 if (!script.starts_with(marker))
2378 auto const newline = script.find(
'\n');
2379 if (newline == std::string_view::npos)
2381 auto const body = script.substr(newline + 1);
2382 auto const bodyStart = body.find_first_not_of(
" \t\r\n");
2383 return bodyStart == std::string_view::npos || body.substr(bodyStart).starts_with(
"--");
2386 for (
auto const& query: queries)
2388 if (isCommentOnlyGuardScript(query))
2389 throw std::runtime_error(
2390 std::format(
"SqlStatement::MigrateDirect cannot apply this SQLite schema change directly because it "
2391 "requires a table rebuild (e.g. ALTER COLUMN or a foreign-key change). Apply it through "
2392 "MigrationManager::ApplyPendingMigrations, which runs the rebuild executor.\n Script: {}",
2394 ExecuteDirect(query, location).ConsumePendingResults(location);
2401template <
typename T>
2402 requires(!std::same_as<T, SqlVariant>)
2406 RequireSuccess(FetchRow());
2407 return GetNullableColumn<T>(1);
2410template <
typename T>
2411 requires(std::same_as<T, SqlVariant>)
2415 RequireSuccess(FetchRow());
2416 if (
auto result = GetNullableColumn<T>(1); result.has_value())
2421template <
typename T>
2422 requires(!std::same_as<T, SqlVariant>)
2425 return ExecuteDirectScalar<T>(query.ToSql(), location);
2428template <
typename T>
2429 requires(std::same_as<T, SqlVariant>)
2432 return ExecuteDirectScalar<T>(query.ToSql(), location);
2435inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::CloseCursor() noexcept
2440 ResetPrefetchState();
2455 auto const rc = SQLMoreResults(m_hStmt);
2456 if (rc == SQL_NO_DATA || !SQL_SUCCEEDED(rc))
2459 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 ConsumePendingResults(std::source_location location=std::source_location::current())
Steps through every result the last execution still has pending and throws for the first one that fai...
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.
LIGHTWEIGHT_API std::expected< SQLSMALLINT, SqlErrorInfo > ColumnSqlType(SQLUSMALLINT column) const
The SQL data type the driver reports for a result column (the SQL_* value from SQLDescribeCol),...
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
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.