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