Lightweight 0.20260522.0
Loading...
Searching...
No Matches
Field.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../DataBinder/Core.hpp"
5#include "../DataBinder/SqlDate.hpp"
6#include "../DataBinder/SqlDateTime.hpp"
7#include "../DataBinder/SqlNumeric.hpp"
8#include "../DataBinder/SqlText.hpp"
9#include "../DataBinder/SqlTime.hpp"
10#include "../Utils.hpp"
11
12#include <reflection-cpp/reflection.hpp>
13
14#include <iomanip>
15#include <optional>
16#include <sstream>
17
18namespace Lightweight
19{
20
21/// @brief Tells the data mapper that this field is a primary key with given semantics, or not a primary key.
22enum class PrimaryKey : uint8_t
23{
24 /// The field is not a primary key.
25 No,
26
27 /// @brief The field is a primary key.
28 ///
29 /// If the field is an auto-incrementable key and not manually set, it is automatically set to the
30 /// next available value on the client side, using a SELECT MAX() query.
31 /// This is happening transparently to the user.
32 ///
33 /// If the field is a GUID, it is automatically set to a new GUID value, if not manually set.
34 ///
35 /// @note If the field is neither auto-incrementable nor a GUID, it must be manually set.
36 AutoAssign,
37
38 /// The field is an integer primary key, and it is auto-incremented by the database.
39 ServerSideAutoIncrement,
40};
41
42namespace detail
43{
44
45 // clang-format off
46
47template <typename T>
48struct IsStdOptionalType: std::false_type {};
49
50template <typename T>
51struct IsStdOptionalType<std::optional<T>>: std::true_type {};
52
53template <typename T>
54constexpr bool IsStdOptional = IsStdOptionalType<T>::value;
55
56template <typename T>
57concept FieldElementType = SqlInputParameterBinder<T> && SqlOutputColumnBinder<T>;
58
59 // clang-format on
60
61 template <typename TargetType, typename P1, typename P2>
62 consteval auto Choose(TargetType defaultValue, P1 p1, P2 p2) noexcept
63 {
64 if constexpr (!std::same_as<P1, std::nullopt_t> && requires { TargetType { p1 }; })
65 return p1;
66 else if constexpr (!std::same_as<P2, std::nullopt_t> && requires { TargetType { p2 }; })
67 return p2;
68 else
69 return defaultValue;
70 }
71} // namespace detail
72
73/// @brief Represents a single column in a table.
74///
75/// This class is used to represent a single column in a table.
76/// It also keeps track of modified-state of the field.
77///
78/// The column name, index, nullability, and type are known at compile time.
79///
80/// @see DataMapper
81/// @ingroup DataMapper, DataTypes
82template <detail::FieldElementType T, auto P1 = std::nullopt, auto P2 = std::nullopt>
83struct Field
84{
85 /// The underlying value type of this field.
86 using ValueType = T;
87
88 /// The primary key mode for this field.
89 static constexpr auto IsPrimaryKeyValue = detail::Choose<PrimaryKey>(PrimaryKey::No, P1, P2);
90 /// If not empty, overrides the default column name in the database.
91 static constexpr auto ColumnNameOverride = detail::Choose<std::string_view>({}, P1, P2);
92
93 // clang-format off
94 constexpr Field() noexcept = default;
95 /// Default copy constructor.
96 constexpr Field(Field const&) noexcept = default;
97 /// Default copy assignment operator.
98 constexpr Field& operator=(Field const&) noexcept = default;
99 /// Default move constructor.
100 constexpr Field(Field&&) noexcept = default;
101 /// Default move assignment operator.
102 constexpr Field& operator=(Field&&) noexcept = default;
103 constexpr ~Field() noexcept = default;
104 // clang-format on
105
106 /// Constructs a new field with the given value.
107 template <typename... S>
108 requires std::constructible_from<T, S...>
109 constexpr Field(S&&... value) noexcept;
110
111 /// Assigns a new value to the field.
112 template <typename S>
113 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
114 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature)
115 constexpr Field& operator=(S&& value) noexcept;
116
117 /// Indicates if the field is optional, i.e., it can be NULL.
118 static constexpr auto IsOptional = detail::IsStdOptional<T>;
119
120 /// Indicates if the field is mandatory, i.e., it cannot be NULL.
121 static constexpr auto IsMandatory = !IsOptional;
122
123 /// Indicates if the field is a primary key.
124 static constexpr auto IsPrimaryKey = IsPrimaryKeyValue != PrimaryKey::No;
125
126 /// Indicates if this is a primary key, it also is auto-assigned by the client.
127 static constexpr auto IsAutoAssignPrimaryKey = IsPrimaryKeyValue == PrimaryKey::AutoAssign;
128
129 /// Indicates if this is a primary key, it also is auto-incremented by the database.
130 static constexpr auto IsAutoIncrementPrimaryKey = IsPrimaryKeyValue == PrimaryKey::ServerSideAutoIncrement;
131
132 /// Three-way comparison operator. The strength of the ordering comes from the underlying
133 /// type `T` — e.g. `Field<int>` is `std::strong_ordering`, `Field<double>` is
134 /// `std::partial_ordering` (because of NaN), `Field<std::string>` is `std::strong_ordering`.
135 /// Defined inline so the deduced return type is available at every call site, and so
136 /// `Field<T>` instantiation does not require `T` to have `operator<=>`.
137 constexpr auto operator<=>(Field const& other) const noexcept
138 requires requires(T const& a, T const& b) { a <=> b; }
139 {
140 return _value <=> other._value;
141 }
142
143 /// Compares the field value with the given value for equality.
144 constexpr bool operator==(Field const& other) const noexcept;
145
146 /// Compares the field value with the given value for inequality.
147 constexpr bool operator!=(Field const& other) const noexcept;
148
149 /// Compares the field value with the given value for equality.
150 template <typename S>
151 requires std::convertible_to<S, T>
152 constexpr bool operator==(S const& value) const noexcept;
153
154 /// Compares the field value with the given value for inequality.
155 template <typename S>
156 requires std::convertible_to<S, T>
157 constexpr bool operator!=(S const& value) const noexcept;
158
159 /// Returns a string representation of the value, suitable for use in debugging and logging.
160 [[nodiscard]] std::string InspectValue() const;
161
162 /// Sets the modified state of the field.
163 constexpr void SetModified(bool value) noexcept;
164
165 /// Checks if the field has been modified.
166 [[nodiscard]] constexpr bool IsModified() const noexcept;
167
168 /// Returns the value of the field.
169 [[nodiscard]] constexpr T const& Value() const noexcept;
170
171 /// When the field type is optional, returns the value or the given default value.
172 [[nodiscard]] constexpr auto ValueOr(auto&& defaultValue) const noexcept
173 requires IsOptional
174 {
175 return _value.value_or(std::forward<typename ValueType::value_type>(defaultValue));
176 }
177
178 /// Returns a mutable reference to the value of the field.
179 ///
180 /// @note If the field value is changed through this method, it will not be automatically marked as modified.
181 [[nodiscard]] constexpr T& MutableValue() noexcept;
182
183 private:
184 ValueType _value {};
185 bool _modified { true };
186};
187
188// clang-format off
189namespace detail
190{
191
192template <typename T>
193struct IsAutoAssignPrimaryKeyField: std::false_type {};
194
195template <typename T, auto P>
196struct IsAutoAssignPrimaryKeyField<Field<T, PrimaryKey::AutoAssign, P>>: std::true_type {};
197
198template <typename T, auto P>
199struct IsAutoAssignPrimaryKeyField<Field<T, P, PrimaryKey::AutoAssign>>: std::true_type {};
200
201template <typename T>
202struct IsAutoIncrementPrimaryKeyField: std::false_type {};
203
204template <typename T, auto P>
205struct IsAutoIncrementPrimaryKeyField<Field<T, PrimaryKey::ServerSideAutoIncrement, P>>: std::true_type {};
206
207template <typename T, auto P>
208struct IsAutoIncrementPrimaryKeyField<Field<T, P, PrimaryKey::ServerSideAutoIncrement>>: std::true_type {};
209
210template <typename T>
211struct IsFieldType: std::false_type {};
212
213template <typename T, auto P1, auto P2>
214struct IsFieldType<Field<T, P1, P2>>: std::true_type {};
215
216} // namespace detail
217// clang-format on
218
219/// Tests if T is a Field<> that is a primary key.
220template <typename T>
221constexpr bool IsPrimaryKey =
222 detail::IsAutoAssignPrimaryKeyField<T>::value || detail::IsAutoIncrementPrimaryKeyField<T>::value;
223
224/// Requires that T satisfies to be a field with storage and is considered a primary key.
225template <typename T>
226constexpr bool IsAutoIncrementPrimaryKey = detail::IsAutoIncrementPrimaryKeyField<T>::value;
227
228template <typename T>
229constexpr bool IsField = detail::IsFieldType<std::remove_cvref_t<T>>::value;
230
231/// Constructs a new field with the given value.
232template <detail::FieldElementType T, auto P1, auto P2>
233template <typename... S>
234 requires std::constructible_from<T, S...>
235constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::Field(S&&... value) noexcept:
236 _value(std::forward<S>(value)...)
237{
238}
239
240/// @copydoc Field::operator=(S&&)
241template <detail::FieldElementType T, auto P1, auto P2>
242template <typename S>
243 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
244constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>& Field<T, P1, P2>::operator=(S&& value) noexcept
245{
246 _value = std::forward<S>(value);
247 SetModified(true);
248 return *this;
249}
250
251template <detail::FieldElementType T, auto P1, auto P2>
252constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(Field const& other) const noexcept
253{
254 return _value == other._value;
255}
256
257template <detail::FieldElementType T, auto P1, auto P2>
258constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(Field const& other) const noexcept
259{
260 return _value != other._value;
261}
262
263/// Equality comparison operator with a convertible value.
264template <detail::FieldElementType T, auto P1, auto P2>
265template <typename S>
266 requires std::convertible_to<S, T>
267constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(S const& value) const noexcept
268{
269 return _value == value;
270}
271
272/// Inequality comparison operator with a convertible value.
273template <detail::FieldElementType T, auto P1, auto P2>
274template <typename S>
275 requires std::convertible_to<S, T>
276constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(S const& value) const noexcept
277{
278 return _value != value;
279}
280
281template <detail::FieldElementType T, auto P1, auto P2>
282inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
283{
284 if constexpr (std::is_same_v<T, std::string>)
285 {
286 std::stringstream result;
287 result << std::quoted(_value, '\'');
288 return result.str();
289 }
290 else if constexpr (std::is_same_v<T, SqlText>)
291 {
292 std::stringstream result;
293 result << std::quoted(_value.value, '\'');
294 return result.str();
295 }
296 else if constexpr (std::is_same_v<T, SqlDate>)
297 return std::format("\'{}\'", _value.value());
298 else if constexpr (std::is_same_v<T, SqlTime>)
299 return std::format("\'{}\'", _value.value());
300 else if constexpr (std::is_same_v<T, SqlDateTime>)
301 return std::format("\'{}\'", _value.value());
302 else if constexpr (SqlNumericType<T>)
303 return std::format("{}", _value.ToString());
304 else if constexpr (requires { _value.has_value(); })
305 {
306 if (_value.has_value())
307 return std::format("{}", _value.value());
308 else
309 return "NULL";
310 }
311 else
312 return std::format("{}", _value);
313}
314
315// ------------------------------------------------------------------------------------------------
316
317template <detail::FieldElementType T, auto P1, auto P2>
318constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
319{
320 _modified = value;
321}
322
323template <detail::FieldElementType T, auto P1, auto P2>
324constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
325{
326 return _modified;
327}
328
329template <detail::FieldElementType T, auto P1, auto P2>
330constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
331{
332 return _value;
333}
334
335template <detail::FieldElementType T, auto P1, auto P2>
336constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
337{
338 return _value;
339}
340
341template <detail::FieldElementType T, auto P1, auto P2>
342struct SqlDataBinder<Field<T, P1, P2>>
343{
344 using ValueType = Field<T, P1, P2>;
345
346 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
347
348 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
349 SQLUSMALLINT column,
350 ValueType const& value,
352 {
353 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
354 }
355
356 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
357 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
358 {
359 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
360 }
361
362 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
363 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
364 {
365 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
366 }
367
368 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
369 {
370 return value.InspectValue();
371 }
372};
373
374/// @brief Retrieves the type of a member field in a record.
375///
376/// Field must be a member of the record type, and it must be a field type, e.g. `Field<int>` or `BelongsTo<OtherRecord>`.
377///
378/// @code
379/// using MyRecord = Record {
380/// Field<int> value;
381/// Field<std::optional<char>> optionalValue;
382/// };
383///
384/// using MyFieldType = ReferencedFieldTypeOf<&MyRecord::value>; // Retrieves `int`
385/// using MyOptionalFieldType = ReferencedFieldTypeOf<&MyRecord::optionalValue>; // Retrieves `std::optional<char>`
386/// @endcode
387///
388/// @ingroup DataMapper
389template <auto Field>
391#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
392 typename std::remove_cvref_t<typename[:type_of(Field):]>::ValueType;
393#else
394 std::remove_cvref_t<decltype(std::declval<MemberClassType<decltype(Field)>>().*Field)>::ValueType;
395#endif
396
397} // namespace Lightweight
398
399template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
400struct std::formatter<Lightweight::Field<T, P1, P2>>: std::formatter<T>
401{
402 template <typename FormatContext>
403 // NOLINTNEXTLINE(readability-identifier-naming)
404 auto format(Lightweight::Field<T, P1, P2> const& field, FormatContext& ctx) const
405 {
406 return formatter<T>::format(field.Value(), ctx);
407 }
408};
409
410/// Specialization for `Field<std::optional<T>, ...>`: `std::optional` has no
411/// `std::formatter` specialization in the standard library, so we inherit from
412/// the inner `std::formatter<T>` and render `"NULL"` for the empty case.
413template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
414struct std::formatter<Lightweight::Field<std::optional<T>, P1, P2>>: std::formatter<T>
415{
416 template <typename FormatContext>
417 // NOLINTNEXTLINE(readability-identifier-naming)
418 auto format(Lightweight::Field<std::optional<T>, P1, P2> const& field, FormatContext& ctx) const
419 {
420 if (field.Value().has_value())
421 return formatter<T>::format(field.Value().value(), ctx);
422
423 // Fallback for the NULL case — write "NULL" verbatim through the format context.
424 constexpr std::string_view nullText { "NULL" };
425 return std::ranges::copy(nullText, ctx.out()).out;
426 }
427};
std::remove_cvref_t< decltype(std::declval< MemberClassType< decltype(Field)> >().*Field)>::ValueType ReferencedFieldTypeOf
Retrieves the type of a member field in a record.
Definition Field.hpp:394
Represents a single column in a table.
Definition Field.hpp:84
T ValueType
The underlying value type of this field.
Definition Field.hpp:86
constexpr bool operator==(S const &value) const noexcept
Compares the field value with the given value for equality.
constexpr bool IsModified() const noexcept
Checks if the field has been modified.
Definition Field.hpp:324
constexpr T & MutableValue() noexcept
Definition Field.hpp:336
constexpr bool operator!=(S const &value) const noexcept
Compares the field value with the given value for inequality.
constexpr bool operator!=(Field const &other) const noexcept
Compares the field value with the given value for inequality.
Definition Field.hpp:258
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:318
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:330
constexpr bool operator==(Field const &other) const noexcept
Compares the field value with the given value for equality.
Definition Field.hpp:252
static constexpr auto IsPrimaryKey
Indicates if the field is a primary key.
Definition Field.hpp:124
static constexpr auto IsMandatory
Indicates if the field is mandatory, i.e., it cannot be NULL.
Definition Field.hpp:121
static constexpr auto IsPrimaryKeyValue
The primary key mode for this field.
Definition Field.hpp:89
constexpr auto ValueOr(auto &&defaultValue) const noexcept
When the field type is optional, returns the value or the given default value.
Definition Field.hpp:172
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:118
static constexpr auto IsAutoAssignPrimaryKey
Indicates if this is a primary key, it also is auto-assigned by the client.
Definition Field.hpp:127
std::string InspectValue() const
Returns a string representation of the value, suitable for use in debugging and logging.
Definition Field.hpp:282
static constexpr auto ColumnNameOverride
If not empty, overrides the default column name in the database.
Definition Field.hpp:91
static constexpr auto IsAutoIncrementPrimaryKey
Indicates if this is a primary key, it also is auto-incremented by the database.
Definition Field.hpp:130