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