Lightweight 0.20251202.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 constexpr bool operator==(Field const& other) const noexcept;
130
131 /// Compares the field value with the given value for inequality.
132 constexpr bool operator!=(Field const& other) const noexcept;
133
134 /// Compares the field value with the given value for equality.
135 template <typename S>
136 requires std::convertible_to<S, T>
137 constexpr 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 constexpr 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 { true };
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>
241constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(Field const& other) const noexcept
242{
243 return _value == other._value;
244}
245
246template <detail::FieldElementType T, auto P1, auto P2>
247constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(Field const& other) const noexcept
248{
249 return _value != other._value;
250}
251
252template <detail::FieldElementType T, auto P1, auto P2>
253template <typename S>
254 requires std::convertible_to<S, T>
255constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(S const& value) const noexcept
256{
257 return _value == value;
258}
259
260template <detail::FieldElementType T, auto P1, auto P2>
261template <typename S>
262 requires std::convertible_to<S, T>
263constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(S const& value) const noexcept
264{
265 return _value != value;
266}
267
268template <detail::FieldElementType T, auto P1, auto P2>
269inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
270{
271 if constexpr (std::is_same_v<T, std::string>)
272 {
273 std::stringstream result;
274 result << std::quoted(_value, '\'');
275 return result.str();
276 }
277 else if constexpr (std::is_same_v<T, SqlText>)
278 {
279 std::stringstream result;
280 result << std::quoted(_value.value, '\'');
281 return result.str();
282 }
283 else if constexpr (std::is_same_v<T, SqlDate>)
284 return std::format("\'{}\'", _value.value);
285 else if constexpr (std::is_same_v<T, SqlTime>)
286 return std::format("\'{}\'", _value.value);
287 else if constexpr (std::is_same_v<T, SqlDateTime>)
288 return std::format("\'{}\'", _value.value());
289 else if constexpr (SqlNumericType<T>)
290 return std::format("{}", _value.ToString());
291 else if constexpr (requires { _value.has_value(); })
292 {
293 if (_value.has_value())
294 return std::format("{}", _value.value());
295 else
296 return "NULL";
297 }
298 else
299 return std::format("{}", _value);
300}
301
302// ------------------------------------------------------------------------------------------------
303
304template <detail::FieldElementType T, auto P1, auto P2>
305constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
306{
307 _modified = value;
308}
309
310template <detail::FieldElementType T, auto P1, auto P2>
311constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
312{
313 return _modified;
314}
315
316template <detail::FieldElementType T, auto P1, auto P2>
317constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
318{
319 return _value;
320}
321
322template <detail::FieldElementType T, auto P1, auto P2>
323constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
324{
325 return _value;
326}
327
328template <detail::FieldElementType T, auto P1, auto P2>
329struct SqlDataBinder<Field<T, P1, P2>>
330{
331 using ValueType = Field<T, P1, P2>;
332
333 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
334
335 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
336 SQLUSMALLINT column,
337 ValueType const& value,
339 {
340 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
341 }
342
343 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
344 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
345 {
346 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
347 }
348
349 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
350 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
351 {
352 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
353 }
354
355 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
356 {
357 return value.InspectValue();
358 }
359};
360
361/// @brief Retrieves the type of a member field in a record.
362///
363/// Field must be a member of the record type, and it must be a field type, e.g. `Field<int>` or `BelongsTo<OtherRecord>`.
364///
365/// @code
366/// using MyRecord = Record {
367/// Field<int> value;
368/// Field<std::optional<char>> optionalValue;
369/// };
370///
371/// using MyFieldType = ReferencedFieldTypeOf<&MyRecord::value>; // Retrieves `int`
372/// using MyOptionalFieldType = ReferencedFieldTypeOf<&MyRecord::optionalValue>; // Retrieves `std::optional<char>`
373/// @endcode
374///
375/// @ingroup DataMapper
376template <auto Field>
378#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
379 typename std::remove_cvref_t<typename[:type_of(Field):]>::ValueType;
380#else
381 std::remove_cvref_t<decltype(std::declval<MemberClassType<decltype(Field)>>().*Field)>::ValueType;
382#endif
383
384} // namespace Lightweight
385
386template <Lightweight::detail::FieldElementType T, auto P1, auto P2>
387struct std::formatter<Lightweight::Field<T, P1, P2>>: std::formatter<T>
388{
389 template <typename FormatContext>
390 // NOLINTNEXTLINE(readability-identifier-naming)
391 auto format(Lightweight::Field<T, P1, P2> const& field, FormatContext& ctx)
392 {
393 return formatter<T>::format(field.InspectValue(), ctx);
394 }
395};
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:381
Represents a single column in a table.
Definition Field.hpp:84
constexpr Field(S &&... value) noexcept
Constructs a new field with the given value.
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:311
constexpr T & MutableValue() noexcept
Definition Field.hpp:323
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:247
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:305
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:317
constexpr bool operator==(Field const &other) const noexcept
Compares the field value with the given value for equality.
Definition Field.hpp:241
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
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:269
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