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