Lightweight 0.20250627.0
Loading...
Searching...
No Matches
Utils.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#if defined(_WIN32) || defined(_WIN64)
6 #include <Windows.h>
7#endif
8
9#include <Lightweight/Lightweight.hpp>
10
11#include <catch2/catch_session.hpp>
12#include <catch2/catch_test_macros.hpp>
13
14#include <algorithm>
15#include <chrono>
16#include <format>
17#include <ostream>
18#include <ranges>
19#include <regex>
20#include <string>
21#include <string_view>
22#include <tuple>
23#include <variant>
24#include <vector>
25
26#if __has_include(<stacktrace>)
27 #include <stacktrace>
28#endif
29
30#include <sql.h>
31#include <sqlext.h>
32#include <sqlspi.h>
33#include <sqltypes.h>
34
35// NOTE: I've done this preprocessor stuff only to have a single test for UTF-16 (UCS-2) regardless of platform.
36using WideChar = std::conditional_t<sizeof(wchar_t) == 2, wchar_t, char16_t>;
37using WideString = std::basic_string<WideChar>;
38using WideStringView = std::basic_string_view<WideChar>;
39
40#if !defined(_WIN32)
41 #define WTEXT(x) (u##x)
42#else
43 #define WTEXT(x) (L##x)
44#endif
45
46#define UNSUPPORTED_DATABASE(stmt, dbType) \
47 if ((stmt).Connection().ServerType() == (dbType)) \
48 { \
49 WARN(std::format("TODO({}): This database is currently unsupported on this test.", dbType)); \
50 return; \
51 }
52
53template <>
54struct std::formatter<std::u8string>: std::formatter<std::string>
55{
56 auto format(std::u8string const& value, std::format_context& ctx) const -> std::format_context::iterator
57 {
58 return std::formatter<std::string>::format(
59 std::format("{}", (char const*) value.c_str()), // NOLINT(readability-redundant-string-cstr)
60 ctx);
61 }
62};
63
64namespace std
65{
66
67// Add support for std::basic_string<WideChar> and std::basic_string_view<WideChar> to std::ostream,
68// so that we can get them pretty-printed in REQUIRE() and CHECK() macros.
69
70template <typename WideStringT>
71 requires(detail::OneOf<WideStringT,
72 std::wstring,
73 std::wstring_view,
74 std::u16string,
75 std::u16string_view,
76 std::u32string,
77 std::u32string_view>)
78ostream& operator<<(ostream& os, WideStringT const& str)
79{
80 auto constexpr BitsPerChar = sizeof(typename WideStringT::value_type) * 8;
81 auto const u8String = ToUtf8(str);
82 return os << "UTF-" << BitsPerChar << '{' << "length: " << str.size() << ", characters: " << '"'
83 << string_view((char const*) u8String.data(), u8String.size()) << '"' << '}';
84}
85
86inline ostream& operator<<(ostream& os, SqlGuid const& guid)
87{
88 return os << format("SqlGuid({})", guid);
89}
90
91} // namespace std
92
93template <std::size_t Precision, std::size_t Scale>
94std::ostream& operator<<(std::ostream& os, SqlNumeric<Precision, Scale> const& value)
95{
96 return os << std::format("SqlNumeric<{}, {}>({}, {}, {}, {})",
97 Precision,
98 Scale,
99 value.sqlValue.sign,
100 value.sqlValue.precision,
101 value.sqlValue.scale,
102 value.ToUnscaledValue());
103}
104
105// Refer to an in-memory SQLite database (and assuming the sqliteodbc driver is installed)
106// See:
107// - https://www.sqlite.org/inmemorydb.html
108// - http://www.ch-werner.de/sqliteodbc/
109// - https://github.com/softace/sqliteodbc
110//
111auto inline const DefaultTestConnectionString = SqlConnectionString {
112 .value = std::format("DRIVER={};Database={}",
113#if defined(_WIN32) || defined(_WIN64)
114 "SQLite3 ODBC Driver",
115#else
116 "SQLite3",
117#endif
118 "file::memory:"),
119};
120
121class TestSuiteSqlLogger: public SqlLogger::Null
122{
123 private:
124 std::string m_lastPreparedQuery;
125
126 template <typename... Args>
127 void WriteInfo(std::format_string<Args...> const& fmt, Args&&... args)
128 {
129 auto message = std::format(fmt, std::forward<Args>(args)...);
130 message = std::format("[{}] {}", "Lightweight", message);
131 try
132 {
133 UNSCOPED_INFO(message);
134 }
135 catch (...)
136 {
137 std::println("{}", message);
138 }
139 }
140
141 template <typename... Args>
142 void WriteWarning(std::format_string<Args...> const& fmt, Args&&... args)
143 {
144 WARN(std::format(fmt, std::forward<Args>(args)...));
145 }
146
147 public:
148 static TestSuiteSqlLogger& GetLogger() noexcept
149 {
150 static TestSuiteSqlLogger theLogger;
151 return theLogger;
152 }
153
154 void OnError(SqlError error, std::source_location sourceLocation) override
155 {
156 WriteWarning("SQL Error: {}", error);
157 WriteDetails(sourceLocation);
158 }
159
160 void OnError(SqlErrorInfo const& errorInfo, std::source_location sourceLocation) override
161 {
162 WriteWarning("SQL Error: {}", errorInfo);
163 WriteDetails(sourceLocation);
164 }
165
166 void OnWarning(std::string_view const& message) override
167 {
168 WriteWarning("{}", message);
169 WriteDetails(std::source_location::current());
170 }
171
172 void OnExecuteDirect(std::string_view const& query) override
173 {
174 WriteInfo("ExecuteDirect: {}", query);
175 }
176
177 void OnPrepare(std::string_view const& query) override
178 {
179 m_lastPreparedQuery = query;
180 }
181
182 void OnExecute(std::string_view const& query) override
183 {
184 WriteInfo("Execute: {}", query);
185 }
186
187 void OnExecuteBatch() override
188 {
189 WriteInfo("ExecuteBatch: {}", m_lastPreparedQuery);
190 }
191
192 void OnFetchRow() override
193 {
194 WriteInfo("Fetched row");
195 }
196
197 void OnFetchEnd() override
198 {
199 WriteInfo("Fetch end");
200 }
201
202 private:
203 void WriteDetails(std::source_location sourceLocation)
204 {
205 WriteInfo(" Source: {}:{}", sourceLocation.file_name(), sourceLocation.line());
206 if (!m_lastPreparedQuery.empty())
207 WriteInfo(" Query: {}", m_lastPreparedQuery);
208 WriteInfo(" Stack trace:");
209
210#if __has_include(<stacktrace>)
211 auto stackTrace = std::stacktrace::current(1, 25);
212 for (std::size_t const i: std::views::iota(std::size_t(0), stackTrace.size()))
213 WriteInfo(" [{:>2}] {}", i, stackTrace[i]);
214#endif
215 }
216};
217
218// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
219class ScopedSqlNullLogger: public SqlLogger::Null
220{
221 private:
222 SqlLogger& m_previousLogger = SqlLogger::GetLogger();
223
224 public:
225 ScopedSqlNullLogger()
226 {
228 }
229
230 ~ScopedSqlNullLogger() override
231 {
232 SqlLogger::SetLogger(m_previousLogger);
233 }
234};
235
236template <typename Getter, typename Callable>
237constexpr void FixedPointIterate(Getter const& getter, Callable const& callable)
238{
239 auto a = getter();
240 for (;;)
241 {
242 callable(a);
243 auto b = getter();
244 if (a == b)
245 break;
246 a = std::move(b);
247 }
248}
249
250// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
251class SqlTestFixture
252{
253 public:
254 static inline std::string testDatabaseName = "LightweightTest";
255 static inline bool odbcTrace = false;
256
257 using MainProgramArgs = std::tuple<int, char**>;
258
259 static std::variant<MainProgramArgs, int> Initialize(int argc, char** argv)
260 {
261 SqlLogger::SetLogger(TestSuiteSqlLogger::GetLogger());
262
263 using namespace std::string_view_literals;
264 int i = 1;
265 for (; i < argc; ++i)
266 {
267 if (argv[i] == "--trace-sql"sv)
269 else if (argv[i] == "--trace-odbc"sv)
270 odbcTrace = true;
271 else if (argv[i] == "--help"sv || argv[i] == "-h"sv)
272 {
273 std::println("{} [--trace-sql] [--trace-odbc] [[--] [Catch2 flags ...]]", argv[0]);
274 return { EXIT_SUCCESS };
275 }
276 else if (argv[i] == "--"sv)
277 {
278 ++i;
279 break;
280 }
281 else
282 break;
283 }
284
285 if (i < argc)
286 argv[i - 1] = argv[0];
287
288#if defined(_MSC_VER)
289 char* envBuffer = nullptr;
290 size_t envBufferLen = 0;
291 _dupenv_s(&envBuffer, &envBufferLen, "ODBC_CONNECTION_STRING");
292 if (auto const* s = envBuffer; s && *s)
293#else
294 if (auto const* s = std::getenv("ODBC_CONNECTION_STRING"); s && *s)
295#endif
296
297 {
298 std::println("Using ODBC connection string: '{}'", SqlConnectionString::SanitizePwd(s));
300 }
301 else
302 {
303 // Use an in-memory SQLite3 database by default (for testing purposes)
304 std::println("Using default ODBC connection string: '{}'", DefaultTestConnectionString.value);
305 SqlConnection::SetDefaultConnectionString(DefaultTestConnectionString);
306 }
307
308 SqlConnection::SetPostConnectedHook(&SqlTestFixture::PostConnectedHook);
309
310 auto sqlConnection = SqlConnection();
311 if (!sqlConnection.IsAlive())
312 {
313 std::println("Failed to connect to the database: {}", sqlConnection.LastError());
314 std::abort();
315 }
316
317 std::println("Running test cases against: {} ({}) (identified as: {})",
318 sqlConnection.ServerName(),
319 sqlConnection.ServerVersion(),
320 sqlConnection.ServerType());
321
322 return MainProgramArgs { argc - (i - 1), argv + (i - 1) };
323 }
324
325 static void PostConnectedHook(SqlConnection& connection)
326 {
327 if (odbcTrace)
328 {
329 auto const traceFile = []() -> std::string_view {
330#if !defined(_WIN32) && !defined(_WIN64)
331 return "/dev/stdout";
332#else
333 return "CONOUT$";
334#endif
335 }();
336
337 SQLHDBC handle = connection.NativeHandle();
338 SQLSetConnectAttrA(handle, SQL_ATTR_TRACEFILE, (SQLPOINTER) traceFile.data(), SQL_NTS);
339 SQLSetConnectAttrA(handle, SQL_ATTR_TRACE, (SQLPOINTER) SQL_OPT_TRACE_ON, SQL_IS_UINTEGER);
340 }
341
342 switch (connection.ServerType())
343 {
344 case SqlServerType::SQLITE: {
345 auto stmt = SqlStatement { connection };
346 // Enable foreign key constraints for SQLite
347 stmt.ExecuteDirect("PRAGMA foreign_keys = ON");
348 break;
349 }
350 case SqlServerType::MICROSOFT_SQL:
351 case SqlServerType::POSTGRESQL:
352 case SqlServerType::ORACLE:
353 case SqlServerType::MYSQL:
354 case SqlServerType::UNKNOWN:
355 break;
356 }
357 }
358
359 SqlTestFixture()
360 {
361 auto stmt = SqlStatement();
362 REQUIRE(stmt.IsAlive());
363
364 // On Github CI, we use the pre-created database "FREEPDB1" for Oracle
365 char dbName[100]; // Buffer to store the database name
366 SQLSMALLINT dbNameLen {};
367 SQLGetInfo(stmt.Connection().NativeHandle(), SQL_DATABASE_NAME, dbName, sizeof(dbName), &dbNameLen);
368 if (dbNameLen > 0)
369 testDatabaseName = dbName;
370 else if (stmt.Connection().ServerType() == SqlServerType::ORACLE)
371 testDatabaseName = "FREEPDB1";
372
373 DropAllTablesInDatabase(stmt);
374 }
375
376 virtual ~SqlTestFixture() = default;
377
378 static std::string ToString(std::vector<std::string> const& values, std::string_view separator)
379 {
380 auto result = std::string {};
381 for (auto const& value: values)
382 {
383 if (!result.empty())
384 result += separator;
385 result += value;
386 }
387 return result;
388 }
389
390 static void DropTableRecursively(SqlStatement& stmt, SqlSchema::FullyQualifiedTableName const& table)
391 {
392 auto const dependantTables = SqlSchema::AllForeignKeysTo(stmt, table);
393 for (auto const& dependantTable: dependantTables)
394 DropTableRecursively(stmt, dependantTable.foreignKey.table);
395 stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\"", table.table));
396 }
397
398 static void DropAllTablesInDatabase(SqlStatement& stmt)
399 {
400 switch (stmt.Connection().ServerType())
401 {
402 case SqlServerType::MICROSOFT_SQL:
403 case SqlServerType::MYSQL:
404 stmt.ExecuteDirect(std::format("USE \"{}\"", testDatabaseName));
405 [[fallthrough]];
406 case SqlServerType::SQLITE:
407 case SqlServerType::ORACLE:
408 case SqlServerType::UNKNOWN: {
409 auto const tableNames = GetAllTableNames(stmt);
410 for (auto const& tableName: tableNames)
411 DropTableRecursively(stmt,
412 SqlSchema::FullyQualifiedTableName {
413 .catalog = {},
414 .schema = {},
415 .table = tableName,
416 });
417 break;
418 }
419 case SqlServerType::POSTGRESQL:
420 if (m_createdTables.empty())
421 m_createdTables = GetAllTableNames(stmt);
422 for (auto& createdTable: std::views::reverse(m_createdTables))
423 stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\" CASCADE", createdTable));
424 break;
425 }
426 m_createdTables.clear();
427 }
428
429 private:
430 static std::vector<std::string> GetAllTableNamesForOracle(SqlStatement& stmt)
431 {
432 auto result = std::vector<std::string> {};
433 stmt.Prepare(R"SQL(SELECT table_name
434 FROM user_tables
435 WHERE table_name NOT LIKE '%$%'
436 AND table_name NOT IN ('SCHEDULER_JOB_ARGS_TBL', 'SCHEDULER_PROGRAM_ARGS_TBL', 'SQLPLUS_PRODUCT_PROFILE')
437 ORDER BY table_name)SQL");
438 stmt.Execute();
439 while (stmt.FetchRow())
440 {
441 result.emplace_back(stmt.GetColumn<std::string>(1));
442 }
443 return result;
444 }
445
446 static std::vector<std::string> GetAllTableNames(SqlStatement& stmt)
447 {
448 if (stmt.Connection().ServerType() == SqlServerType::ORACLE)
449 return GetAllTableNamesForOracle(stmt);
450
451 using namespace std::string_literals;
452 auto result = std::vector<std::string>();
453 auto const schemaName = [&] {
454 switch (stmt.Connection().ServerType())
455 {
456 case SqlServerType::MICROSOFT_SQL:
457 return "dbo"s;
458 default:
459 return ""s;
460 }
461 }();
462 auto const sqlResult = SQLTables(stmt.NativeHandle(),
463 (SQLCHAR*) testDatabaseName.data(),
464 (SQLSMALLINT) testDatabaseName.size(),
465 (SQLCHAR*) schemaName.data(),
466 (SQLSMALLINT) schemaName.size(),
467 nullptr,
468 0,
469 (SQLCHAR*) "TABLE",
470 SQL_NTS);
471 if (SQL_SUCCEEDED(sqlResult))
472 {
473 while (stmt.FetchRow())
474 {
475 result.emplace_back(stmt.GetColumn<std::string>(3)); // table name
476 }
477 }
478 return result;
479 }
480
481 static inline std::vector<std::string> m_createdTables;
482};
483
484// {{{ ostream support for Lightweight, for debugging purposes
485inline std::ostream& operator<<(std::ostream& os, SqlText const& value)
486{
487 return os << std::format("SqlText({})", value.value);
488}
489
490inline std::ostream& operator<<(std::ostream& os, SqlDate const& date)
491{
492 auto const ymd = date.value();
493 return os << std::format("SqlDate {{ {}-{}-{} }}", ymd.year(), ymd.month(), ymd.day());
494}
495
496inline std::ostream& operator<<(std::ostream& os, SqlTime const& time)
497{
498 auto const value = time.value();
499 return os << std::format("SqlTime {{ {:02}:{:02}:{:02}.{:06} }}",
500 value.hours().count(),
501 value.minutes().count(),
502 value.seconds().count(),
503 value.subseconds().count());
504}
505
506inline std::ostream& operator<<(std::ostream& os, SqlDateTime const& datetime)
507{
508 auto const value = datetime.value();
509 auto const totalDays = std::chrono::floor<std::chrono::days>(value);
510 auto const ymd = std::chrono::year_month_day { totalDays };
511 auto const hms =
512 std::chrono::hh_mm_ss<std::chrono::nanoseconds> { std::chrono::floor<std::chrono::nanoseconds>(value - totalDays) };
513 return os << std::format("SqlDateTime {{ {:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} }}",
514 (int) ymd.year(),
515 (unsigned) ymd.month(),
516 (unsigned) ymd.day(),
517 hms.hours().count(),
518 hms.minutes().count(),
519 hms.seconds().count(),
520 hms.subseconds().count());
521}
522
523template <std::size_t N, typename T, SqlFixedStringMode Mode>
524inline std::ostream& operator<<(std::ostream& os, SqlFixedString<N, T, Mode> const& value)
525{
526 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE)
527 return os << std::format("SqlFixedString<{}> {{ size: {}, data: '{}' }}", N, value.size(), value.data());
528 else if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
529 return os << std::format("SqlTrimmedFixedString<{}> {{ '{}' }}", N, value.data());
530 else if constexpr (Mode == SqlFixedStringMode::VARIABLE_SIZE)
531 {
532 if constexpr (std::same_as<T, char>)
533 return os << std::format("SqlVariableString<{}> {{ size: {}, '{}' }}", N, value.size(), value.data());
534 else
535 {
536 auto u8String = ToUtf8(std::basic_string_view<T>(value.data(), value.size()));
537 return os << std::format("SqlVariableString<{}, {}> {{ size: {}, '{}' }}",
538 N,
539 Reflection::TypeNameOf<T>,
540 value.size(),
541 (char const*) u8String.c_str()); // NOLINT(readability-redundant-string-cstr)
542 }
543 }
544 else
545 return os << std::format("SqlFixedString<{}> {{ size: {}, data: '{}' }}", N, value.size(), value.data());
546}
547
548template <std::size_t N, typename T>
549inline std::ostream& operator<<(std::ostream& os, SqlDynamicString<N, T> const& value)
550{
551 if constexpr (std::same_as<T, char>)
552 return os << std::format("SqlDynamicString<{}> {{ size: {}, '{}' }}", N, value.size(), value.data());
553 else
554 {
555 auto u8String = ToUtf8(std::basic_string_view<T>(value.data(), value.size()));
556 return os << std::format("SqlDynamicString<{}, {}> {{ size: {}, '{}' }}",
557 N,
558 Reflection::TypeNameOf<T>,
559 value.size(),
560 (char const*) u8String.c_str()); // NOLINT(readability-redundant-string-cstr)
561 }
562}
563
564[[nodiscard]] inline std::string NormalizeText(std::string_view const& text)
565{
566 auto result = std::string(text);
567
568 // Remove any newlines and reduce all whitespace to a single space
569 result.erase(std::unique(result.begin(), // NOLINT(modernize-use-ranges)
570 result.end(),
571 [](char a, char b) { return std::isspace(a) && std::isspace(b); }),
572 result.end());
573
574 // trim lading and trailing whitespace
575 while (!result.empty() && std::isspace(result.front()))
576 result.erase(result.begin());
577
578 while (!result.empty() && std::isspace(result.back()))
579 result.pop_back();
580
581 return result;
582}
583
584[[nodiscard]] inline std::string NormalizeText(std::vector<std::string> const& texts)
585{
586 auto result = std::string {};
587 for (auto const& text: texts)
588 {
589 if (!result.empty())
590 result += '\n';
591 result += NormalizeText(text);
592 }
593 return result;
594}
595
596// }}}
597
598inline void CreateEmployeesTable(SqlStatement& stmt, std::source_location location = std::source_location::current())
599{
600 stmt.MigrateDirect(
601 [](SqlMigrationQueryBuilder& migration) {
602 migration.CreateTable("Employees")
603 .PrimaryKeyWithAutoIncrement("EmployeeID")
604 .RequiredColumn("FirstName", SqlColumnTypeDefinitions::Varchar { 50 })
605 .Column("LastName", SqlColumnTypeDefinitions::Varchar { 50 })
606 .RequiredColumn("Salary", SqlColumnTypeDefinitions::Integer {});
607 },
608 location);
609}
610
611inline void CreateLargeTable(SqlStatement& stmt)
612{
613 stmt.MigrateDirect([](SqlMigrationQueryBuilder& migration) {
614 auto table = migration.CreateTable("LargeTable");
615 for (char c = 'A'; c <= 'Z'; ++c)
616 {
617 table.Column(std::string(1, c), SqlColumnTypeDefinitions::Varchar { 50 });
618 }
619 });
620}
621
622inline void FillEmployeesTable(SqlStatement& stmt)
623{
624 stmt.Prepare(stmt.Query("Employees")
625 .Insert()
626 .Set("FirstName", SqlWildcard)
627 .Set("LastName", SqlWildcard)
628 .Set("Salary", SqlWildcard));
629 stmt.Execute("Alice", "Smith", 50'000);
630 stmt.Execute("Bob", "Johnson", 60'000);
631 stmt.Execute("Charlie", "Brown", 70'000);
632}
633
634template <typename T = char>
635inline auto MakeLargeText(size_t size)
636{
637 auto text = std::basic_string<T>(size, {});
638 std::ranges::generate(text, [i = 0]() mutable { return static_cast<T>('A' + (i++ % 26)); });
639 return text;
640}
641
642inline bool IsGithubActions()
643{
644#if defined(_WIN32) || defined(_WIN64)
645 char envBuffer[32] {};
646 size_t requiredCount = 0;
647 return getenv_s(&requiredCount, envBuffer, sizeof(envBuffer), "GITHUB_ACTIONS") == 0
648 && std::string_view(envBuffer) == "true" == 0;
649#else
650 return std::getenv("GITHUB_ACTIONS") != nullptr && std::string_view(std::getenv("GITHUB_ACTIONS")) == "true";
651#endif
652}
Represents a connection to a SQL database.
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
static LIGHTWEIGHT_API void SetDefaultConnectionString(SqlConnectionString const &connectionString) noexcept
SQLHDBC NativeHandle() const noexcept
Retrieves the native handle.
static LIGHTWEIGHT_API void SetPostConnectedHook(std::function< void(SqlConnection &)> hook)
Sets a callback to be called after each connection being established.
LIGHTWEIGHT_API SqlCreateTableQueryBuilder & Column(SqlColumnDeclaration column)
Adds a new column to the table.
LIGHTWEIGHT_FORCE_INLINE T const * data() const noexcept
Retrieves the string's inner value (as T const*).
LIGHTWEIGHT_FORCE_INLINE std::size_t size() const noexcept
Retrieves the string's size.
LIGHTWEIGHT_FORCE_INLINE constexpr std::size_t size() const noexcept
Returns the size of the string.
Represents a logger for SQL operations.
Definition SqlLogger.hpp:19
static LIGHTWEIGHT_API void SetLogger(SqlLogger &logger)
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
static LIGHTWEIGHT_API SqlLogger & TraceLogger()
Retrieves a logger that logs to the trace logger.
Query builder for building SQL migration queries.
Definition Migrate.hpp:182
LIGHTWEIGHT_API SqlCreateTableQueryBuilder CreateTable(std::string_view tableName)
Creates a new table.
LIGHTWEIGHT_API SqlInsertQueryBuilder Insert(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept
Retrieves the native handle of the statement.
void Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
LIGHTWEIGHT_API void ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
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 bool FetchRow()
LIGHTWEIGHT_API void Prepare(std::string_view query) &
bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const &table={}) const
Creates a new query builder for the given table, compatible with the SQL server being connected.
LIGHTWEIGHT_API std::u8string ToUtf8(std::u32string_view u32InputString)
Represents an ODBC connection string.
constexpr LIGHTWEIGHT_FORCE_INLINE native_type value() const noexcept
Returns the current date and time.
LIGHTWEIGHT_FORCE_INLINE constexpr std::chrono::year_month_day value() const noexcept
Returns the current date.
Definition SqlDate.hpp:26
Represents an ODBC SQL error.
Definition SqlError.hpp:29
constexpr LIGHTWEIGHT_FORCE_INLINE auto ToUnscaledValue() const noexcept
Converts the numeric to an unscaled integer value.
SQL_NUMERIC_STRUCT sqlValue
The value is stored as a string to avoid floating point precision issues.