Lightweight 0.20250904.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 using ValueType = T;
86
87 static constexpr auto IsPrimaryKeyValue = detail::Choose<PrimaryKey>(PrimaryKey::No, P1, P2);
88 static constexpr auto ColumnNameOverride = detail::Choose<std::string_view>({}, P1, P2);
89
90 // clang-format off
91 constexpr Field() noexcept = default;
92 constexpr Field(Field const&) noexcept = default;
93 constexpr Field& operator=(Field const&) noexcept = default;
94 constexpr Field(Field&&) noexcept = default;
95 constexpr Field& operator=(Field&&) noexcept = default;
96 constexpr ~Field() noexcept = default;
97 // clang-format on
98
99 /// Constructs a new field with the given value.
100 template <typename... S>
101 requires std::constructible_from<T, S...>
102 constexpr Field(S&&... value) noexcept;
103
104 /// Assigns a new value to the field.
105 template <typename S>
106 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
107 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature)
108 constexpr Field& operator=(S&& value) noexcept;
109
110 /// Indicates if the field is optional, i.e., it can be NULL.
111 static constexpr auto IsOptional = detail::IsStdOptional<T>;
112
113 /// Indicates if the field is mandatory, i.e., it cannot be NULL.
114 static constexpr auto IsMandatory = !IsOptional;
115
116 /// Indicates if the field is a primary key.
117 static constexpr auto IsPrimaryKey = IsPrimaryKeyValue != PrimaryKey::No;
118
119 /// Indicates if this is a primary key, it also is auto-assigned by the client.
120 static constexpr auto IsAutoAssignPrimaryKey = IsPrimaryKeyValue == PrimaryKey::AutoAssign;
121
122 /// Indicates if this is a primary key, it also is auto-incremented by the database.
123 static constexpr auto IsAutoIncrementPrimaryKey = IsPrimaryKeyValue == PrimaryKey::ServerSideAutoIncrement;
124
125 /// Compares two fields for equality.
126 constexpr std::weak_ordering operator<=>(Field const& other) const noexcept;
127
128 /// Compares the field value with the given value for equality.
129 bool operator==(Field const& value) const noexcept = default;
130
131 /// Compares the field value with the given value for inequality.
132 bool operator!=(Field const& value) const noexcept = default;
133
134 /// Compares the field value with the given value for equality.
135 template <typename S>
136 requires std::convertible_to<S, T>
137 bool operator==(S const& value) const noexcept;
138
139 /// Compares the field value with the given value for inequality.
140 template <typename S>
141 requires std::convertible_to<S, T>
142 bool operator!=(S const& value) const noexcept;
143
144 /// Returns a string representation of the value, suitable for use in debugging and logging.
145 [[nodiscard]] std::string InspectValue() const;
146
147 /// Sets the modified state of the field.
148 constexpr void SetModified(bool value) noexcept;
149
150 /// Checks if the field has been modified.
151 [[nodiscard]] constexpr bool IsModified() const noexcept;
152
153 /// Returns the value of the field.
154 [[nodiscard]] constexpr T const& Value() const noexcept;
155
156 /// Returns a mutable reference to the value of the field.
157 ///
158 /// @note If the field value is changed through this method, it will not be automatically marked as modified.
159 [[nodiscard]] constexpr T& MutableValue() noexcept;
160
161 private:
162 ValueType _value {};
163 bool _modified { false };
164};
165
166// clang-format off
167namespace detail
168{
169
170template <typename T>
171struct IsAutoAssignPrimaryKeyField: std::false_type {};
172
173template <typename T, auto P>
174struct IsAutoAssignPrimaryKeyField<Field<T, PrimaryKey::AutoAssign, P>>: std::true_type {};
175
176template <typename T, auto P>
177struct IsAutoAssignPrimaryKeyField<Field<T, P, PrimaryKey::AutoAssign>>: std::true_type {};
178
179template <typename T>
180struct IsAutoIncrementPrimaryKeyField: std::false_type {};
181
182template <typename T, auto P>
183struct IsAutoIncrementPrimaryKeyField<Field<T, PrimaryKey::ServerSideAutoIncrement, P>>: std::true_type {};
184
185template <typename T, auto P>
186struct IsAutoIncrementPrimaryKeyField<Field<T, P, PrimaryKey::ServerSideAutoIncrement>>: std::true_type {};
187
188template <typename T>
189struct IsFieldType: std::false_type {};
190
191template <typename T, auto P1, auto P2>
192struct IsFieldType<Field<T, P1, P2>>: std::true_type {};
193
194} // namespace detail
195// clang-format on
196
197/// Tests if T is a Field<> that is a primary key.
198template <typename T>
199constexpr bool IsPrimaryKey =
200 detail::IsAutoAssignPrimaryKeyField<T>::value || detail::IsAutoIncrementPrimaryKeyField<T>::value;
201
202/// Requires that T satisfies to be a field with storage and is considered a primary key.
203template <typename T>
204constexpr bool IsAutoIncrementPrimaryKey = detail::IsAutoIncrementPrimaryKeyField<T>::value;
205
206template <typename T>
207constexpr bool IsField = detail::IsFieldType<std::remove_cvref_t<T>>::value;
208
209template <detail::FieldElementType T, auto P1, auto P2>
210template <typename... S>
211 requires std::constructible_from<T, S...>
212constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::Field(S&&... value) noexcept:
213 _value(std::forward<S>(value)...)
214{
215}
216
217template <detail::FieldElementType T, auto P1, auto P2>
218template <typename S>
219 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
220constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>& Field<T, P1, P2>::operator=(S&& value) noexcept
221{
222 _value = std::forward<S>(value);
223 SetModified(true);
224 return *this;
225}
226
227template <detail::FieldElementType T, auto P1, auto P2>
228constexpr std::weak_ordering LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator<=>(Field const& other) const noexcept
229{
230 return _value <=> other._value;
231}
232
233template <detail::FieldElementType T, auto P1, auto P2>
234template <typename S>
235 requires std::convertible_to<S, T>
236inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator==(S const& value) const noexcept
237{
238 return _value == value;
239}
240
241template <detail::FieldElementType T, auto P1, auto P2>
242template <typename S>
243 requires std::convertible_to<S, T>
244inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator!=(S const& value) const noexcept
245{
246 return _value != value;
247}
248
249template <detail::FieldElementType T, auto P1, auto P2>
250inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
251{
252 if constexpr (std::is_same_v<T, std::string>)
253 {
254 std::stringstream result;
255 result << std::quoted(_value, '\'');
256 return result.str();
257 }
258 else if constexpr (std::is_same_v<T, SqlText>)
259 {
260 std::stringstream result;
261 result << std::quoted(_value.value, '\'');
262 return result.str();
263 }
264 else if constexpr (std::is_same_v<T, SqlDate>)
265 return std::format("\'{}\'", _value.value);
266 else if constexpr (std::is_same_v<T, SqlTime>)
267 return std::format("\'{}\'", _value.value);
268 else if constexpr (std::is_same_v<T, SqlDateTime>)
269 return std::format("\'{}\'", _value.value());
270 else if constexpr (SqlNumericType<T>)
271 return std::format("{}", _value.ToString());
272 else if constexpr (requires { _value.has_value(); })
273 {
274 if (_value.has_value())
275 return std::format("{}", _value.value());
276 else
277 return "NULL";
278 }
279 else
280 return std::format("{}", _value);
281}
282
283// ------------------------------------------------------------------------------------------------
284
285template <detail::FieldElementType T, auto P1, auto P2>
286constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
287{
288 _modified = value;
289}
290
291template <detail::FieldElementType T, auto P1, auto P2>
292constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
293{
294 return _modified;
295}
296
297template <detail::FieldElementType T, auto P1, auto P2>
298constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
299{
300 return _value;
301}
302
303template <detail::FieldElementType T, auto P1, auto P2>
304constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
305{
306 return _value;
307}
308
309template <detail::FieldElementType T, auto P1, auto P2>
310struct SqlDataBinder<Field<T, P1, P2>>
311{
312 using ValueType = Field<T, P1, P2>;
313
314 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
315
316 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
317 SQLUSMALLINT column,
318 ValueType const& value,
320 {
321 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
322 }
323
324 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
325 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
326 {
327 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
328 }
329
330 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
331 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
332 {
333 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
334 }
335
336 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
337 {
338 return value.InspectValue();
339 }
340};
341
342/// @brief Retrieves the type of a member field in a record.
343///
344/// Field must be a member of the record type, and it must be a field type, e.g. `Field<int>` or `BelongsTo<OtherRecord>`.
345///
346/// @code
347/// using MyRecord = Record {
348/// Field<int> value;
349/// Field<std::optional<char>> optionalValue;
350/// };
351///
352/// using MyFieldType = ReferencedFieldTypeOf<&MyRecord::value>; // Retrieves `int`
353/// using MyOptionalFieldType = ReferencedFieldTypeOf<&MyRecord::optionalValue>; // Retrieves `std::optional<char>`
354/// @endcode
355///
356/// @ingroup DataMapper
357template <auto Field>
359#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
360 typename std::remove_cvref_t<typename[:type_of(Field):]>::ValueType;
361#else
362 typename std::remove_cvref_t<decltype(std::declval<MemberClassType<decltype(Field)>>().*Field)>::ValueType;
363#endif
364
365} // namespace Lightweight
366
367template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
368struct std::formatter<Lightweight::Field<T, P1, P2>>: std::formatter<T>
369{
370 template <typename FormatContext>
371 // NOLINTNEXTLINE(readability-identifier-naming)
372 auto format(Lightweight::Field<T, P1, P2> const& field, FormatContext& ctx)
373 {
374 return formatter<T>::format(field.InspectValue(), ctx);
375 }
376};
typename 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:362
Represents a single column in a table.
Definition Field.hpp:84
constexpr Field(S &&... value) noexcept
Constructs a new field with the given value.
bool operator!=(S const &value) const noexcept
Compares the field value with the given value for inequality.
constexpr bool IsModified() const noexcept
Checks if the field has been modified.
Definition Field.hpp:292
constexpr T & MutableValue() noexcept
Definition Field.hpp:304
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:286
bool operator==(S const &value) const noexcept
Compares the field value with the given value for equality.
bool operator!=(Field const &value) const noexcept=default
Compares the field value with the given value for inequality.
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:298
static constexpr auto IsPrimaryKey
Indicates if the field is a primary key.
Definition Field.hpp:117
static constexpr auto IsMandatory
Indicates if the field is mandatory, i.e., it cannot be NULL.
Definition Field.hpp:114
bool operator==(Field const &value) const noexcept=default
Compares the field value with the given value for equality.
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:111
static constexpr auto IsAutoAssignPrimaryKey
Indicates if this is a primary key, it also is auto-assigned by the client.
Definition Field.hpp:120
std::string InspectValue() const
Returns a string representation of the value, suitable for use in debugging and logging.
Definition Field.hpp:250
constexpr std::weak_ordering operator<=>(Field const &other) const noexcept
Compares two fields for equality.
Definition Field.hpp:228
static constexpr auto IsAutoIncrementPrimaryKey
Indicates if this is a primary key, it also is auto-incremented by the database.
Definition Field.hpp:123