Lightweight 0.20261002.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. For a key
36 /// the mapper must never generate even when it could, use @c Manual.
37 AutoAssign,
38
39 /// The field is an integer primary key, and it is auto-incremented by the database.
40 ServerSideAutoIncrement,
41
42 /// @brief The field is a primary key whose value the caller always supplies.
43 ///
44 /// The data mapper neither generates it nor writes a generated value into it, so this is the
45 /// assignment for every member of a composite primary key: auto-assignment produces one value,
46 /// which a composite key cannot be built from.
47 Manual,
48};
49
50/// @brief Tells the data mapper that this field is a timestamp it maintains automatically, or not.
51///
52/// Pass it in one of a Field's two option slots, alone or together with a column-name override:
53///
54/// @code
55/// struct Article
56/// {
57/// Field<uint64_t, PrimaryKey::ServerSideAutoIncrement> id;
58/// Field<SqlDateTime, FieldTimestamp::CreatedAt> createdAt;
59/// Field<SqlDateTime, FieldTimestamp::UpdatedAt, SqlRealName { "updated_at" }> updatedAt;
60/// };
61/// @endcode
62///
63/// The value is read from the DataMapper's timestamp clock (see DataMapper::SetTimestampClock()), once
64/// per operation, so every timestamp written by one operation carries the same value. A value the
65/// caller set explicitly is never overwritten, which allows importing historical rows.
66///
67/// Only `SqlDateTime` and `std::optional<SqlDateTime>` fields may carry a timestamp marker.
68///
69/// @ingroup DataMapper
70enum class FieldTimestamp : uint8_t
71{
72 /// The field is not maintained by the data mapper.
73 None,
74
75 /// @brief The field holds the time the record was created.
76 ///
77 /// Set when the record is inserted, unless the caller gave it a value. Never changed by an update.
79
80 /// @brief The field holds the time the record was last written.
81 ///
82 /// Set when the record is inserted, unless the caller gave it a value, and again whenever an update
83 /// writes any other column of the record, unless the caller modified this field in that change.
85};
86
87namespace detail
88{
89
90 // clang-format off
91
92template <typename T>
93struct IsStdOptionalType: std::false_type {};
94
95template <typename T>
96struct IsStdOptionalType<std::optional<T>>: std::true_type {};
97
98template <typename T>
99constexpr bool IsStdOptional = IsStdOptionalType<T>::value;
100
101template <typename T>
102concept FieldElementType = SqlInputParameterBinder<T> && SqlOutputColumnBinder<T>;
103
104 // clang-format on
105
106 template <typename TargetType, typename P1, typename P2>
107 consteval auto Choose(TargetType defaultValue, P1 p1, P2 p2) noexcept
108 {
109 if constexpr (!std::same_as<P1, std::nullopt_t> && requires { TargetType { p1 }; })
110 return p1;
111 else if constexpr (!std::same_as<P2, std::nullopt_t> && requires { TargetType { p2 }; })
112 return p2;
113 else
114 return defaultValue;
115 }
116
117 /// Whether @p T is a value type a @ref FieldTimestamp marker may be attached to.
118 template <typename T>
119 inline constexpr bool IsAutoTimestampValueType = OneOf<T, SqlDateTime, std::optional<SqlDateTime>>;
120
121 /// @brief Whether an auto-maintained timestamp value is unset, i.e. was never given a value.
122 ///
123 /// An optional timestamp is unset when it holds no value; a plain `SqlDateTime` is unset when it
124 /// has no date, as a default-constructed one has (no valid date has a zero month).
125 ///
126 /// @param value The timestamp value to test.
127 /// @return True if the data mapper may fill in the value.
128 template <typename T>
129 requires IsAutoTimestampValueType<T>
130 [[nodiscard]] constexpr bool IsUnsetTimestamp(T const& value) noexcept
131 {
132 if constexpr (IsStdOptional<T>)
133 return !value.has_value();
134 else
135 return value.sqlValue.month == 0;
136 }
137} // namespace detail
138
139/// @brief Represents a single column in a table.
140///
141/// This class is used to represent a single column in a table.
142/// It also keeps track of modified-state of the field.
143///
144/// The column name, index, nullability, and type are known at compile time.
145///
146/// @see DataMapper
147/// @ingroup DataMapper, DataTypes
148template <detail::FieldElementType T, auto P1 = std::nullopt, auto P2 = std::nullopt>
149struct Field
150{
151 /// The underlying value type of this field.
152 using ValueType = T;
153
154 /// The primary key mode for this field.
155 static constexpr auto IsPrimaryKeyValue = detail::Choose<PrimaryKey>(PrimaryKey::No, P1, P2);
156 /// If not empty, overrides the default column name in the database.
157 static constexpr auto ColumnNameOverride = detail::Choose<std::string_view>({}, P1, P2);
158 /// Which timestamp, if any, the data mapper maintains in this field.
159 static constexpr auto TimestampKind = detail::Choose<FieldTimestamp>(FieldTimestamp::None, P1, P2);
160
161 /// Indicates if the data mapper sets this field to the creation time of the record.
163
164 /// Indicates if the data mapper sets this field to the time the record was last written.
166
167 /// Indicates if the data mapper maintains this field as a timestamp of either kind.
169
170 static_assert(!IsAutoTimestamp || detail::IsAutoTimestampValueType<T>,
171 "A FieldTimestamp marker (CreatedAt / UpdatedAt) requires the field's value type to be SqlDateTime "
172 "or std::optional<SqlDateTime>.");
173
174 // clang-format off
175 constexpr Field() noexcept = default;
176 /// Default copy constructor.
177 constexpr Field(Field const&) noexcept = default;
178 /// Default copy assignment operator.
179 constexpr Field& operator=(Field const&) noexcept = default;
180 /// Default move constructor.
181 constexpr Field(Field&&) noexcept = default;
182 /// Default move assignment operator.
183 constexpr Field& operator=(Field&&) noexcept = default;
184 constexpr ~Field() noexcept = default;
185 // clang-format on
186
187 /// Constructs a new field with the given value.
188 template <typename... S>
189 requires std::constructible_from<T, S...>
190 constexpr Field(S&&... value) noexcept;
191
192 /// Assigns a new value to the field.
193 template <typename S>
194 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
195 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature)
196 constexpr Field& operator=(S&& value) noexcept;
197
198 /// Indicates if the field is optional, i.e., it can be NULL.
199 static constexpr auto IsOptional = detail::IsStdOptional<T>;
200
201 /// Indicates if the field is mandatory, i.e., it cannot be NULL.
202 static constexpr auto IsMandatory = !IsOptional;
203
204 /// Indicates if the field is a primary key.
205 static constexpr auto IsPrimaryKey = IsPrimaryKeyValue != PrimaryKey::No;
206
207 /// Indicates if this is a primary key, it also is auto-assigned by the client.
208 static constexpr auto IsAutoAssignPrimaryKey = IsPrimaryKeyValue == PrimaryKey::AutoAssign;
209
210 /// Indicates if this is a primary key, it also is auto-incremented by the database.
211 static constexpr auto IsAutoIncrementPrimaryKey = IsPrimaryKeyValue == PrimaryKey::ServerSideAutoIncrement;
212
213 /// Three-way comparison operator. The strength of the ordering comes from the underlying
214 /// type `T` — e.g. `Field<int>` is `std::strong_ordering`, `Field<double>` is
215 /// `std::partial_ordering` (because of NaN), `Field<std::string>` is `std::strong_ordering`.
216 /// Defined inline so the deduced return type is available at every call site, and so
217 /// `Field<T>` instantiation does not require `T` to have `operator<=>`.
218 constexpr auto operator<=>(Field const& other) const noexcept
219 requires requires(T const& a, T const& b) { a <=> b; }
220 {
221 return _value <=> other._value;
222 }
223
224 /// Compares the field value with the given value for equality.
225 constexpr bool operator==(Field const& other) const noexcept;
226
227 /// Compares the field value with the given value for inequality.
228 constexpr bool operator!=(Field const& other) const noexcept;
229
230 /// Compares the field value with the given value for equality.
231 template <typename S>
232 requires std::convertible_to<S, T>
233 constexpr bool operator==(S const& value) const noexcept;
234
235 /// Compares the field value with the given value for inequality.
236 template <typename S>
237 requires std::convertible_to<S, T>
238 constexpr bool operator!=(S const& value) const noexcept;
239
240 /// Returns a string representation of the value, suitable for use in debugging and logging.
241 [[nodiscard]] std::string InspectValue() const;
242
243 /// Sets the modified state of the field.
244 constexpr void SetModified(bool value) noexcept;
245
246 /// Checks if the field has been modified.
247 [[nodiscard]] constexpr bool IsModified() const noexcept;
248
249 /// Returns the value of the field.
250 [[nodiscard]] constexpr T const& Value() const noexcept;
251
252 /// When the field type is optional, returns the value or the given default value.
253 [[nodiscard]] constexpr auto ValueOr(auto&& defaultValue) const noexcept
254 requires IsOptional
255 {
256 return _value.value_or(std::forward<typename ValueType::value_type>(defaultValue));
257 }
258
259 /// Returns a mutable reference to the value of the field.
260 ///
261 /// @note If the field value is changed through this method, it will not be automatically marked as modified.
262 [[nodiscard]] constexpr T& MutableValue() noexcept;
263
264 private:
265 ValueType _value {};
266 bool _modified { true };
267};
268
269// clang-format off
270namespace detail
271{
272
273template <typename T>
274struct IsAutoAssignPrimaryKeyField: std::false_type {};
275
276template <typename T, auto P>
277struct IsAutoAssignPrimaryKeyField<Field<T, PrimaryKey::AutoAssign, P>>: std::true_type {};
278
279template <typename T, auto P>
280struct IsAutoAssignPrimaryKeyField<Field<T, P, PrimaryKey::AutoAssign>>: std::true_type {};
281
282template <typename T>
283struct IsAutoIncrementPrimaryKeyField: std::false_type {};
284
285template <typename T, auto P>
286struct IsAutoIncrementPrimaryKeyField<Field<T, PrimaryKey::ServerSideAutoIncrement, P>>: std::true_type {};
287
288template <typename T, auto P>
289struct IsAutoIncrementPrimaryKeyField<Field<T, P, PrimaryKey::ServerSideAutoIncrement>>: std::true_type {};
290
291template <typename T>
292struct IsFieldType: std::false_type {};
293
294template <typename T, auto P1, auto P2>
295struct IsFieldType<Field<T, P1, P2>>: std::true_type {};
296
297} // namespace detail
298// clang-format on
299
300/// Tests if T is a Field<> that is a primary key, of whichever @ref PrimaryKey kind.
301template <typename T>
302constexpr bool IsPrimaryKey = false;
303
304template <typename T, auto P1, auto P2>
305constexpr bool IsPrimaryKey<Field<T, P1, P2>> = Field<T, P1, P2>::IsPrimaryKey;
306
307/// Requires that T satisfies to be a field with storage and is considered a primary key.
308template <typename T>
309constexpr bool IsAutoIncrementPrimaryKey = detail::IsAutoIncrementPrimaryKeyField<T>::value;
310
311template <typename T>
312constexpr bool IsField = detail::IsFieldType<std::remove_cvref_t<T>>::value;
313
314/// The @ref FieldTimestamp kind of @p T: the field's marker if T is a Field<>, otherwise None.
315template <typename T>
316inline constexpr FieldTimestamp FieldTimestampOf = FieldTimestamp::None;
317
318/// The @ref FieldTimestamp kind of a Field<>.
319template <typename T, auto P1, auto P2>
320inline constexpr FieldTimestamp FieldTimestampOf<Field<T, P1, P2>> = Field<T, P1, P2>::TimestampKind;
321
322/// Tests if T is a Field<> whose value the data mapper maintains as a timestamp (CreatedAt or UpdatedAt).
323template <typename T>
324inline constexpr bool IsAutoTimestampField = FieldTimestampOf<std::remove_cvref_t<T>> != FieldTimestamp::None;
325
326/// Constructs a new field with the given value.
327template <detail::FieldElementType T, auto P1, auto P2>
328template <typename... S>
329 requires std::constructible_from<T, S...>
330constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::Field(S&&... value) noexcept:
331 _value(std::forward<S>(value)...)
332{
333}
334
335/// @copydoc Field::operator=(S&&)
336template <detail::FieldElementType T, auto P1, auto P2>
337template <typename S>
338 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
339constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>& Field<T, P1, P2>::operator=(S&& value) noexcept
340{
341 _value = std::forward<S>(value);
342 SetModified(true);
343 return *this;
344}
345
346template <detail::FieldElementType T, auto P1, auto P2>
347constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(Field const& other) const noexcept
348{
349 return _value == other._value;
350}
351
352template <detail::FieldElementType T, auto P1, auto P2>
353constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(Field const& other) const noexcept
354{
355 return _value != other._value;
356}
357
358/// Equality comparison operator with a convertible value.
359template <detail::FieldElementType T, auto P1, auto P2>
360template <typename S>
361 requires std::convertible_to<S, T>
362constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(S const& value) const noexcept
363{
364 return _value == value;
365}
366
367/// Inequality comparison operator with a convertible value.
368template <detail::FieldElementType T, auto P1, auto P2>
369template <typename S>
370 requires std::convertible_to<S, T>
371constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(S const& value) const noexcept
372{
373 return _value != value;
374}
375
376template <detail::FieldElementType T, auto P1, auto P2>
377inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
378{
379 if constexpr (std::is_same_v<T, std::string>)
380 {
381 std::stringstream result;
382 result << std::quoted(_value, '\'');
383 return result.str();
384 }
385 else if constexpr (std::is_same_v<T, SqlText>)
386 {
387 std::stringstream result;
388 result << std::quoted(_value.value, '\'');
389 return result.str();
390 }
391 else if constexpr (std::is_same_v<T, SqlDate>)
392 return std::format("\'{}\'", _value.value());
393 else if constexpr (std::is_same_v<T, SqlTime>)
394 return std::format("\'{}\'", _value.value());
395 else if constexpr (std::is_same_v<T, SqlDateTime>)
396 return std::format("\'{}\'", _value.value());
397 else if constexpr (SqlNumericType<T>)
398 return std::format("{}", _value.ToString());
399 else if constexpr (requires { _value.has_value(); })
400 {
401 if (_value.has_value())
402 return std::format("{}", _value.value());
403 else
404 return "NULL";
405 }
406 else
407 return std::format("{}", _value);
408}
409
410// ------------------------------------------------------------------------------------------------
411
412template <detail::FieldElementType T, auto P1, auto P2>
413constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
414{
415 _modified = value;
416}
417
418template <detail::FieldElementType T, auto P1, auto P2>
419constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
420{
421 return _modified;
422}
423
424template <detail::FieldElementType T, auto P1, auto P2>
425constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
426{
427 return _value;
428}
429
430template <detail::FieldElementType T, auto P1, auto P2>
431constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
432{
433 return _value;
434}
435
436template <detail::FieldElementType T, auto P1, auto P2>
437struct SqlDataBinder<Field<T, P1, P2>>
438{
439 using ValueType = Field<T, P1, P2>;
440
441 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
442
443 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
444 SQLUSMALLINT column,
445 ValueType const& value,
447 {
448 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
449 }
450
451 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
452 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
453 {
454 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
455 }
456
457 /// @throws Whatever `SqlDataBinder<T>::GetColumn` throws — this forwards to an arbitrary binder
458 /// and cannot promise more than the one it wraps.
459 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
460 GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb)
461 {
462 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
463 }
464
465 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
466 {
467 return value.InspectValue();
468 }
469};
470
471/// @brief Retrieves the type of a member field in a record.
472///
473/// Field must be a member of the record type, and it must be a field type, e.g. `Field<int>` or `BelongsTo<OtherRecord>`.
474///
475/// @code
476/// using MyRecord = Record {
477/// Field<int> value;
478/// Field<std::optional<char>> optionalValue;
479/// };
480///
481/// using MyFieldType = ReferencedFieldTypeOf<&MyRecord::value>; // Retrieves `int`
482/// using MyOptionalFieldType = ReferencedFieldTypeOf<&MyRecord::optionalValue>; // Retrieves `std::optional<char>`
483/// @endcode
484///
485/// @ingroup DataMapper
486template <auto Field>
488#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
489 typename std::remove_cvref_t<typename[:type_of(Field):]>::ValueType;
490#else
491 std::remove_cvref_t<decltype(std::declval<MemberClassType<decltype(Field)>>().*Field)>::ValueType;
492#endif
493
494} // namespace Lightweight
495
496template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
497struct std::formatter<Lightweight::Field<T, P1, P2>>: std::formatter<T>
498{
499 template <typename FormatContext>
500 // NOLINTNEXTLINE(readability-identifier-naming)
501 auto format(Lightweight::Field<T, P1, P2> const& field, FormatContext& ctx) const
502 {
503 return formatter<T>::format(field.Value(), ctx);
504 }
505};
506
507/// Specialization for `Field<std::optional<T>, ...>`: `std::optional` has no
508/// `std::formatter` specialization in the standard library, so we inherit from
509/// the inner `std::formatter<T>` and render `"NULL"` for the empty case.
510template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
511struct std::formatter<Lightweight::Field<std::optional<T>, P1, P2>>: std::formatter<T>
512{
513 template <typename FormatContext>
514 // NOLINTNEXTLINE(readability-identifier-naming)
515 auto format(Lightweight::Field<std::optional<T>, P1, P2> const& field, FormatContext& ctx) const
516 {
517 if (field.Value().has_value())
518 return formatter<T>::format(field.Value().value(), ctx);
519
520 // Fallback for the NULL case — write "NULL" verbatim through the format context.
521 constexpr std::string_view nullText { "NULL" };
522 return std::ranges::copy(nullText, ctx.out()).out;
523 }
524};
FieldTimestamp
Tells the data mapper that this field is a timestamp it maintains automatically, or not.
Definition Field.hpp:71
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:491
@ UpdatedAt
The field holds the time the record was last written.
@ CreatedAt
The field holds the time the record was created.
@ None
The field is not maintained by the data mapper.
Represents a single column in a table.
Definition Field.hpp:150
T ValueType
The underlying value type of this field.
Definition Field.hpp:152
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:419
constexpr T & MutableValue() noexcept
Definition Field.hpp:431
static constexpr auto TimestampKind
Which timestamp, if any, the data mapper maintains in this field.
Definition Field.hpp:159
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:353
static constexpr auto IsAutoTimestamp
Indicates if the data mapper maintains this field as a timestamp of either kind.
Definition Field.hpp:168
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:413
static constexpr auto IsUpdatedAtTimestamp
Indicates if the data mapper sets this field to the time the record was last written.
Definition Field.hpp:165
static constexpr auto IsCreatedAtTimestamp
Indicates if the data mapper sets this field to the creation time of the record.
Definition Field.hpp:162
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:425
constexpr bool operator==(Field const &other) const noexcept
Compares the field value with the given value for equality.
Definition Field.hpp:347
static constexpr auto IsPrimaryKey
Indicates if the field is a primary key.
Definition Field.hpp:205
static constexpr auto IsMandatory
Indicates if the field is mandatory, i.e., it cannot be NULL.
Definition Field.hpp:202
static constexpr auto IsPrimaryKeyValue
The primary key mode for this field.
Definition Field.hpp:155
constexpr auto ValueOr(auto &&defaultValue) const noexcept
When the field type is optional, returns the value or the given default value.
Definition Field.hpp:253
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:199
static constexpr auto IsAutoAssignPrimaryKey
Indicates if this is a primary key, it also is auto-assigned by the client.
Definition Field.hpp:208
std::string InspectValue() const
Returns a string representation of the value, suitable for use in debugging and logging.
Definition Field.hpp:377
static constexpr auto ColumnNameOverride
If not empty, overrides the default column name in the database.
Definition Field.hpp:157
static constexpr auto IsAutoIncrementPrimaryKey
Indicates if this is a primary key, it also is auto-incremented by the database.
Definition Field.hpp:211