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