Lightweight 0.20260303.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 /// Compares two fields for equality.
133 constexpr std::weak_ordering operator<=>(Field const& other) const noexcept;
134
135 /// Compares the field value with the given value for equality.
136 constexpr bool operator==(Field const& other) const noexcept;
137
138 /// Compares the field value with the given value for inequality.
139 constexpr bool operator!=(Field const& other) const noexcept;
140
141 /// Compares the field value with the given value for equality.
142 template <typename S>
143 requires std::convertible_to<S, T>
144 constexpr bool operator==(S const& value) const noexcept;
145
146 /// Compares the field value with the given value for inequality.
147 template <typename S>
148 requires std::convertible_to<S, T>
149 constexpr bool operator!=(S const& value) const noexcept;
150
151 /// Returns a string representation of the value, suitable for use in debugging and logging.
152 [[nodiscard]] std::string InspectValue() const;
153
154 /// Sets the modified state of the field.
155 constexpr void SetModified(bool value) noexcept;
156
157 /// Checks if the field has been modified.
158 [[nodiscard]] constexpr bool IsModified() const noexcept;
159
160 /// Returns the value of the field.
161 [[nodiscard]] constexpr T const& Value() const noexcept;
162
163 /// When the field type is optional, returns the value or the given default value.
164 [[nodiscard]] constexpr auto ValueOr(auto&& defaultValue) const noexcept
165 requires IsOptional
166 {
167 return _value.value_or(std::forward<typename ValueType::value_type>(defaultValue));
168 }
169
170 /// Returns a mutable reference to the value of the field.
171 ///
172 /// @note If the field value is changed through this method, it will not be automatically marked as modified.
173 [[nodiscard]] constexpr T& MutableValue() noexcept;
174
175 private:
176 ValueType _value {};
177 bool _modified { true };
178};
179
180// clang-format off
181namespace detail
182{
183
184template <typename T>
185struct IsAutoAssignPrimaryKeyField: std::false_type {};
186
187template <typename T, auto P>
188struct IsAutoAssignPrimaryKeyField<Field<T, PrimaryKey::AutoAssign, P>>: std::true_type {};
189
190template <typename T, auto P>
191struct IsAutoAssignPrimaryKeyField<Field<T, P, PrimaryKey::AutoAssign>>: std::true_type {};
192
193template <typename T>
194struct IsAutoIncrementPrimaryKeyField: std::false_type {};
195
196template <typename T, auto P>
197struct IsAutoIncrementPrimaryKeyField<Field<T, PrimaryKey::ServerSideAutoIncrement, P>>: std::true_type {};
198
199template <typename T, auto P>
200struct IsAutoIncrementPrimaryKeyField<Field<T, P, PrimaryKey::ServerSideAutoIncrement>>: std::true_type {};
201
202template <typename T>
203struct IsFieldType: std::false_type {};
204
205template <typename T, auto P1, auto P2>
206struct IsFieldType<Field<T, P1, P2>>: std::true_type {};
207
208} // namespace detail
209// clang-format on
210
211/// Tests if T is a Field<> that is a primary key.
212template <typename T>
213constexpr bool IsPrimaryKey =
214 detail::IsAutoAssignPrimaryKeyField<T>::value || detail::IsAutoIncrementPrimaryKeyField<T>::value;
215
216/// Requires that T satisfies to be a field with storage and is considered a primary key.
217template <typename T>
218constexpr bool IsAutoIncrementPrimaryKey = detail::IsAutoIncrementPrimaryKeyField<T>::value;
219
220template <typename T>
221constexpr bool IsField = detail::IsFieldType<std::remove_cvref_t<T>>::value;
222
223/// Constructs a new field with the given value.
224template <detail::FieldElementType T, auto P1, auto P2>
225template <typename... S>
226 requires std::constructible_from<T, S...>
227constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::Field(S&&... value) noexcept:
228 _value(std::forward<S>(value)...)
229{
230}
231
232/// @copydoc Field::operator=(S&&)
233template <detail::FieldElementType T, auto P1, auto P2>
234template <typename S>
235 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
236constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>& Field<T, P1, P2>::operator=(S&& value) noexcept
237{
238 _value = std::forward<S>(value);
239 SetModified(true);
240 return *this;
241}
242
243template <detail::FieldElementType T, auto P1, auto P2>
244constexpr std::weak_ordering LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator<=>(Field const& other) const noexcept
245{
246 return _value <=> other._value;
247}
248
249template <detail::FieldElementType T, auto P1, auto P2>
250constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(Field const& other) const noexcept
251{
252 return _value == other._value;
253}
254
255template <detail::FieldElementType T, auto P1, auto P2>
256constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(Field const& other) const noexcept
257{
258 return _value != other._value;
259}
260
261/// Equality comparison operator with a convertible value.
262template <detail::FieldElementType T, auto P1, auto P2>
263template <typename S>
264 requires std::convertible_to<S, T>
265constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(S const& value) const noexcept
266{
267 return _value == value;
268}
269
270/// Inequality comparison operator with a convertible value.
271template <detail::FieldElementType T, auto P1, auto P2>
272template <typename S>
273 requires std::convertible_to<S, T>
274constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(S const& value) const noexcept
275{
276 return _value != value;
277}
278
279template <detail::FieldElementType T, auto P1, auto P2>
280inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
281{
282 if constexpr (std::is_same_v<T, std::string>)
283 {
284 std::stringstream result;
285 result << std::quoted(_value, '\'');
286 return result.str();
287 }
288 else if constexpr (std::is_same_v<T, SqlText>)
289 {
290 std::stringstream result;
291 result << std::quoted(_value.value, '\'');
292 return result.str();
293 }
294 else if constexpr (std::is_same_v<T, SqlDate>)
295 return std::format("\'{}\'", _value.value);
296 else if constexpr (std::is_same_v<T, SqlTime>)
297 return std::format("\'{}\'", _value.value);
298 else if constexpr (std::is_same_v<T, SqlDateTime>)
299 return std::format("\'{}\'", _value.value());
300 else if constexpr (SqlNumericType<T>)
301 return std::format("{}", _value.ToString());
302 else if constexpr (requires { _value.has_value(); })
303 {
304 if (_value.has_value())
305 return std::format("{}", _value.value());
306 else
307 return "NULL";
308 }
309 else
310 return std::format("{}", _value);
311}
312
313// ------------------------------------------------------------------------------------------------
314
315template <detail::FieldElementType T, auto P1, auto P2>
316constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
317{
318 _modified = value;
319}
320
321template <detail::FieldElementType T, auto P1, auto P2>
322constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
323{
324 return _modified;
325}
326
327template <detail::FieldElementType T, auto P1, auto P2>
328constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
329{
330 return _value;
331}
332
333template <detail::FieldElementType T, auto P1, auto P2>
334constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
335{
336 return _value;
337}
338
339template <detail::FieldElementType T, auto P1, auto P2>
340struct SqlDataBinder<Field<T, P1, P2>>
341{
342 using ValueType = Field<T, P1, P2>;
343
344 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
345
346 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
347 SQLUSMALLINT column,
348 ValueType const& value,
350 {
351 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
352 }
353
354 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
355 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
356 {
357 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
358 }
359
360 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
361 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
362 {
363 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
364 }
365
366 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
367 {
368 return value.InspectValue();
369 }
370};
371
372/// @brief Retrieves the type of a member field in a record.
373///
374/// Field must be a member of the record type, and it must be a field type, e.g. `Field<int>` or `BelongsTo<OtherRecord>`.
375///
376/// @code
377/// using MyRecord = Record {
378/// Field<int> value;
379/// Field<std::optional<char>> optionalValue;
380/// };
381///
382/// using MyFieldType = ReferencedFieldTypeOf<&MyRecord::value>; // Retrieves `int`
383/// using MyOptionalFieldType = ReferencedFieldTypeOf<&MyRecord::optionalValue>; // Retrieves `std::optional<char>`
384/// @endcode
385///
386/// @ingroup DataMapper
387template <auto Field>
389#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
390 typename std::remove_cvref_t<typename[:type_of(Field):]>::ValueType;
391#else
392 std::remove_cvref_t<decltype(std::declval<MemberClassType<decltype(Field)>>().*Field)>::ValueType;
393#endif
394
395} // namespace Lightweight
396
397template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
398struct std::formatter<Lightweight::Field<T, P1, P2>>: std::formatter<T>
399{
400 template <typename FormatContext>
401 // NOLINTNEXTLINE(readability-identifier-naming)
402 auto format(Lightweight::Field<T, P1, P2> const& field, FormatContext& ctx)
403 {
404 return formatter<T>::format(field.InspectValue(), ctx);
405 }
406};
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:392
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 IsModified() const noexcept
Checks if the field has been modified.
Definition Field.hpp:322
constexpr T & MutableValue() noexcept
Definition Field.hpp:334
constexpr bool operator!=(Field const &other) const noexcept
Compares the field value with the given value for inequality.
Definition Field.hpp:256
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:316
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:328
constexpr bool operator==(Field const &other) const noexcept
Compares the field value with the given value for equality.
Definition Field.hpp:250
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:164
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:280
static constexpr auto ColumnNameOverride
If not empty, overrides the default column name in the database.
Definition Field.hpp:91
constexpr std::weak_ordering operator<=>(Field const &other) const noexcept
Compares two fields for equality.
Definition Field.hpp:244
static constexpr auto IsAutoIncrementPrimaryKey
Indicates if this is a primary key, it also is auto-incremented by the database.
Definition Field.hpp:130