Lightweight 0.20250904.0
Loading...
Searching...
No Matches
Utils.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Api.hpp"
6
7#include <reflection-cpp/reflection.hpp>
8
9#include <algorithm>
10#include <ranges>
11#include <source_location>
12#include <string_view>
13#include <type_traits>
14#include <unordered_map>
15#include <utility>
16
17#include <sql.h>
18
19#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
20 #include <experimental/meta>
21#endif
22
23namespace Lightweight
24{
25
26namespace detail
27{
28
29 template <typename T, typename... Comps>
30 concept OneOf = (std::same_as<T, Comps> || ...);
31
32 template <typename T>
33 constexpr auto AlwaysFalse = std::false_type::value;
34
35 constexpr auto Finally(auto&& cleanupRoutine) noexcept
36 {
37 // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
38 struct Finally
39 {
40 std::remove_cvref_t<decltype(cleanupRoutine)> cleanup;
41 ~Finally()
42 {
43 cleanup();
44 }
45 };
46 return Finally { std::forward<decltype(cleanupRoutine)>(cleanupRoutine) };
47 }
48
49 // is_specialization_of<> is inspired by:
50 // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2098r1.pdf
51
52 template <template <typename...> class T, typename U>
53 struct is_specialization_of: std::false_type
54 {
55 };
56
57 template <template <typename...> class T, typename... Us>
58 struct is_specialization_of<T, T<Us...>>: std::true_type
59 {
60 };
61
62 template <typename T>
63 struct MemberClassTypeHelper;
64
65 template <typename M, typename T>
66 struct MemberClassTypeHelper<M T::*>
67 {
68 using type = std::remove_cvref_t<T>;
69 };
70
71 template <typename Record>
72 struct RecordTableNameImpl
73 {
74 static constexpr std::string_view Value = []() {
75 if constexpr (requires { Record::TableName; })
76 return Record::TableName;
77 else
78 return []() {
79#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
80 return std::meta::identifier_of(^^Record);
81#else
82 auto const typeName = Reflection::TypeNameOf<Record>;
83 if (auto const i = typeName.rfind(':'); i != std::string_view::npos)
84 return typeName.substr(i + 1);
85 return typeName;
86#endif
87 }();
88 }();
89 };
90
91 // specialization for the case when we use tuple as
92 // a record, then we use the first element of the tuple
93 // to get the table name
94 template <typename First, typename Second>
95 struct RecordTableNameImpl<std::tuple<First, Second>>
96 {
97 static constexpr std::string_view Value = []() {
98 if constexpr (requires { First::TableName; })
99 return First::TableName;
100 else
101 return []() {
102#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
103 return std::meta::identifier_of(^^First);
104#else
105 auto const typeName = Reflection::TypeNameOf<First>;
106 if (auto const i = typeName.rfind(':'); i != std::string_view::npos)
107 return typeName.substr(i + 1);
108 return typeName;
109#endif
110 }();
111 }();
112 };
113
114 template <typename FieldType>
115 constexpr auto ColumnNameOverride = []() consteval {
116 if constexpr (requires { FieldType::ColumnNameOverride; })
117 return FieldType::ColumnNameOverride;
118 else
119 return std::string_view {};
120 }();
121#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
122 template <auto reflection>
123 struct FieldNameOfImpl
124 {
125 using R = typename[:std::meta::type_of(reflection):];
126 static constexpr std::string_view value = []() constexpr -> std::string_view {
127 if constexpr (requires { R::ColumnNameOverride; })
128 {
129 if constexpr (!R::ColumnNameOverride.empty())
130 return R::ColumnNameOverride;
131 }
132 return std::meta::identifier_of(reflection);
133 }();
134 };
135#else
136 template <typename ReferencedFieldType, auto F>
137 struct FieldNameOfImpl;
138
139 template <typename T, auto F, typename R>
140 struct FieldNameOfImpl<R T::*, F>
141 {
142 static constexpr std::string_view value = []() constexpr -> std::string_view {
143 if constexpr (requires { R::ColumnNameOverride; })
144 {
145 if constexpr (!R::ColumnNameOverride.empty())
146 return R::ColumnNameOverride;
147 }
148 return Reflection::NameOf<F>;
149 }();
150 };
151#endif // LIGHTWEIGHT_CXX26_REFLECTION
152
153 template <std::size_t I, typename Record>
154 consteval std::string_view FieldNameAt()
155 {
156 using FieldType = Reflection::MemberTypeOf<I, Record>;
157
158 if constexpr (!std::string_view(ColumnNameOverride<FieldType>).empty())
159 {
160 return FieldType::ColumnNameOverride;
161 }
162 return Reflection::MemberNameOf<I, Record>;
163 }
164} // namespace detail
165
166/// @brief Returns the SQL field name of the given field index in the record.
167///
168/// @ingroup DataMapper
169template <std::size_t I, typename Record>
170constexpr inline std::string_view FieldNameAt = detail::FieldNameAt<I, Record>();
171
172/// @brief Holds the SQL tabl ename for the given record type.
173///
174/// @ingroup DataMapper
175template <typename Record>
176constexpr std::string_view RecordTableName = detail::RecordTableNameImpl<Record>::Value;
177
178template <template <typename...> class S, class T>
179concept IsSpecializationOf = detail::is_specialization_of<S, T>::value;
180
181#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
182
183/// @brief Returns the name of the field referenced by the given pointer-to-member.
184///
185/// This also supports custom column name overrides.
186template <std::meta::info ReflectionOfField>
187constexpr inline std::string_view FieldNameOf = detail::FieldNameOfImpl<ReflectionOfField>::value;
188
189template <auto Member>
190using MemberClassType = typename[:std::meta::parent_of(Member):];
191
192template <auto Member>
193constexpr size_t MemberIndexOf = []() consteval -> size_t {
194 int index { -1 };
195 auto members = nonstatic_data_members_of(parent_of(Member), std::meta::access_context::current());
196 if (auto it = std::ranges::find(members, Member); it != members.end())
197 {
198 index = std::distance(members.begin(), it);
199 return static_cast<size_t>(index);
200 }
201 return -1;
202}();
203#else // not LIGHTWEIGHT_CXX26_REFLECTION
204
205/// @brief Returns the name of the field referenced by the given pointer-to-member.
206///
207/// This also supports custom column name overrides.
208template <auto ReferencedField>
209constexpr inline std::string_view FieldNameOf = detail::FieldNameOfImpl<decltype(ReferencedField), ReferencedField>::value;
210
211template <auto Member>
212constexpr size_t MemberIndexOf = Reflection::MemberIndexOf<Member>;
213
214template <typename T>
215using MemberClassType = typename detail::MemberClassTypeHelper<T>::type;
216
217#endif // LIGHTWEIGHT_CXX26_REFLECTION
218
219namespace detail
220{
221 template <auto ReferencedField>
222 struct FullyQualifiedNameOfImpl
223 {
224#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
225 static constexpr auto ClassName = RecordTableName<typename[:std::meta::parent_of(ReferencedField):]>;
226#else
227 static constexpr auto ClassName = RecordTableName<MemberClassType<decltype(ReferencedField)>>;
228#endif
229 static constexpr auto FieldName = FieldNameOf<ReferencedField>;
230 static constexpr auto StorageSize = ClassName.size() + FieldName.size() + 6;
231
232 // Holds the full field name in the format "ClassName"."FieldName"
233 static constexpr auto Storage = []() constexpr -> std::array<char, StorageSize> {
234 // clang-format off
235 auto storage = std::array<char, StorageSize> {};
236 std::ranges::copy("\"", storage.begin());
237 std::ranges::copy(ClassName, storage.begin() + 1);
238 std::ranges::copy("\".\"", storage.begin() + 1 + ClassName.size());
239 std::ranges::copy(FieldName, storage.begin() + 1 + ClassName.size() + 3);
240 std::ranges::copy("\"", storage.begin() + 1 + ClassName.size() + 3 + FieldName.size());
241 storage.back() = '\0';
242 // clang-format on
243 return storage;
244 }();
245 static constexpr auto value = std::string_view(Storage.data(), Storage.size() - 1);
246 };
247} // namespace detail
248
249struct SqlRawColumnNameView
250{
251 std::string_view value;
252
253 std::weak_ordering operator<=>(SqlRawColumnNameView const& other) const = default;
254
255 [[nodiscard]] constexpr auto begin() const noexcept
256 {
257 return value.begin();
258 }
259
260 [[nodiscard]] constexpr auto end() const noexcept
261 {
262 return value.end();
263 }
264
265 [[nodiscard]] constexpr auto size() const noexcept
266 {
267 return value.size();
268 }
269
270 [[nodiscard]] constexpr auto empty() const noexcept
271 {
272 return value.empty();
273 }
274
275 [[nodiscard]] constexpr auto data() const noexcept
276 {
277 return value.data();
278 }
279
280 [[nodiscard]] constexpr std::string_view string_view() const noexcept
281 {
282 return value;
283 }
284
285 [[nodiscard]] constexpr std::string to_string() const
286 {
287 return std::string(value);
288 }
289};
290
291constexpr bool operator==(SqlRawColumnNameView const& lhs, std::string_view rhs) noexcept
292{
293 return lhs.value == rhs;
294}
295
296constexpr bool operator!=(SqlRawColumnNameView const& lhs, std::string_view rhs) noexcept
297{
298 return lhs.value != rhs;
299}
300
301/// @brief Holds the quoted fully qualified field name (including table name) of the given field.
302/// @tparam ReferencedField
303///
304/// @code
305/// auto const quotedFieldName = FullyQualifiedNameOf<&Person::id>;
306/// static_assert(quotedFieldName.value == R"sql("Person"."id")sql");
307/// @endcode
308///
309/// @ingroup DataMapper
310template <auto ReferencedField>
311constexpr inline auto FullyQualifiedNameOf = SqlRawColumnNameView {
312 .value = detail::FullyQualifiedNameOfImpl<ReferencedField>::value,
313};
314
315namespace detail
316{
317 template <auto... ReferencedFields>
318 struct FullyQualifiedNamesOfImpl
319 {
320 static constexpr auto StorageSize =
321 1 + (2 * (sizeof...(ReferencedFields) - 1)) + (0 + ... + FullyQualifiedNameOf<ReferencedFields>.size());
322
323 static constexpr std::array<char, StorageSize> Storage = []() consteval {
324 auto result = std::array<char, StorageSize> {};
325 size_t offset = 0;
326 (
327 [&] {
328 if (offset > 0)
329 {
330 constexpr auto Delimiter = std::string_view(", ");
331 std::ranges::copy(Delimiter, result.begin() + offset);
332 offset += Delimiter.size();
333 }
334 std::ranges::copy(FullyQualifiedNameOf<ReferencedFields>, result.begin() + offset);
335 offset += FullyQualifiedNameOf<ReferencedFields>.size();
336 }(),
337 ...);
338 result.back() = '\0';
339 return result;
340 }();
341
342 static constexpr auto value = std::string_view(Storage.data(), Storage.size() - 1);
343 };
344
345} // namespace detail
346
347/// @brief Holds the quoted fully qualified field names of the given fields.
348///
349/// @code
350/// auto const quotedFieldNames = FullyQualifiedNamesOf<&Person::id, &Person::name, &Person::age>;
351/// static_assert(quotedFieldNames.value == R"sql("Person"."id", "Person"."name", "Person"."age")sql");
352/// @endcode
353///
354/// @ingroup DataMapper
355template <auto... ReferencedFields>
356constexpr inline auto FullyQualifiedNamesOf = SqlRawColumnNameView {
357 .value = detail::FullyQualifiedNamesOfImpl<ReferencedFields...>::value,
358};
359
360LIGHTWEIGHT_API void LogIfFailed(SQLHSTMT hStmt, SQLRETURN error, std::source_location sourceLocation);
361
362LIGHTWEIGHT_API void RequireSuccess(SQLHSTMT hStmt,
363 SQLRETURN error,
364 std::source_location sourceLocation = std::source_location::current());
365
366/// Defines the naming convention for use (e.g. for C++ column names or table names in C++ struct names).
367enum class FormatType : uint8_t
368{
369 /// Preserve the original naming convention.
370 preserve,
371
372 /// Ensure the name is formatted in snake_case naming convention.
373 snakeCase,
374
375 /// Ensure the name is formatted in CamelCase naming convention.
376 camelCase,
377};
378
379/// @brief Converts a string to a format that is more suitable for C++ code.
380LIGHTWEIGHT_API std::string FormatName(std::string const& name, FormatType formatType);
381
382/// @brief Converts a string to a format that is more suitable for C++ code.
383LIGHTWEIGHT_API std::string FormatName(std::string_view name, FormatType formatType);
384
385/// Maintains collisions to create unique names
387{
388 public:
389 /// Tests if the given name is already registered.
390 [[nodiscard]] LIGHTWEIGHT_API bool IsColliding(std::string const& name) const noexcept;
391
392 /// Tries to declare a name and returns it, otherwise returns std::nullopt.
393 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::string> TryDeclareName(std::string name);
394
395 /// Creates a name that is definitely not colliding.
396 [[nodiscard]] LIGHTWEIGHT_API std::string DeclareName(std::string name);
397
398 private:
399 std::unordered_map<std::string, size_t> _collisionMap;
400};
401
402} // namespace Lightweight
Maintains collisions to create unique names.
Definition Utils.hpp:387
LIGHTWEIGHT_API std::string DeclareName(std::string name)
Creates a name that is definitely not colliding.
LIGHTWEIGHT_API bool IsColliding(std::string const &name) const noexcept
Tests if the given name is already registered.
LIGHTWEIGHT_API std::optional< std::string > TryDeclareName(std::string name)
Tries to declare a name and returns it, otherwise returns std::nullopt.
constexpr std::string_view RecordTableName
Holds the SQL tabl ename for the given record type.
Definition Utils.hpp:176
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
Definition Utils.hpp:170
constexpr auto FullyQualifiedNamesOf
Holds the quoted fully qualified field names of the given fields.
Definition Utils.hpp:356
constexpr auto FullyQualifiedNameOf
Holds the quoted fully qualified field name (including table name) of the given field.
Definition Utils.hpp:311