Lightweight 0.20250627.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/SqlText.hpp"
8#include "../DataBinder/SqlTime.hpp"
9
10#include <reflection-cpp/reflection.hpp>
11
12#include <iomanip>
13#include <optional>
14#include <sstream>
15
16/// @brief Tells the data mapper that this field is a primary key with given semantics, or not a primary key.
17enum class PrimaryKey : uint8_t
18{
19 /// The field is not a primary key.
20 No,
21
22 /// @brief The field is a primary key.
23 ///
24 /// If the field is an auto-incrementable key and not manually set, it is automatically set to the
25 /// next available value on the client side, using a SELECT MAX() query.
26 /// This is happening transparently to the user.
27 ///
28 /// If the field is a GUID, it is automatically set to a new GUID value, if not manually set.
29 ///
30 /// @note If the field is neither auto-incrementable nor a GUID, it must be manually set.
31 AutoAssign,
32
33 /// The field is an integer primary key, and it is auto-incremented by the database.
34 ServerSideAutoIncrement,
35};
36
37namespace detail
38{
39
40// clang-format off
41
42template <typename T>
43struct IsStdOptionalType: std::false_type {};
44
45template <typename T>
46struct IsStdOptionalType<std::optional<T>>: std::true_type {};
47
48template <typename T>
49constexpr bool IsStdOptional = IsStdOptionalType<T>::value;
50
51template <typename T>
52concept FieldElementType = SqlInputParameterBinder<T> && SqlOutputColumnBinder<T>;
53
54// clang-format on
55
56template <typename TargetType, typename P1, typename P2>
57consteval auto Choose(TargetType defaultValue, P1 p1, P2 p2) noexcept
58{
59 if constexpr (!std::same_as<P1, std::nullopt_t> && requires { TargetType { p1 }; })
60 return p1;
61 else if constexpr (!std::same_as<P2, std::nullopt_t> && requires { TargetType { p2 }; })
62 return p2;
63 else
64 return defaultValue;
65}
66} // namespace detail
67
68/// @brief Represents a single column in a table.
69///
70/// This class is used to represent a single column in a table.
71/// It also keeps track of modified-state of the field.
72///
73/// The column name, index, nullability, and type are known at compile time.
74///
75/// @see DataMapper
76/// @ingroup DataMapper, DataTypes
77template <detail::FieldElementType T, auto P1 = std::nullopt, auto P2 = std::nullopt>
78struct Field
79{
80 using ValueType = T;
81
82 static constexpr auto IsPrimaryKeyValue = detail::Choose<PrimaryKey>(PrimaryKey::No, P1, P2);
83 static constexpr auto ColumnNameOverride = detail::Choose<std::string_view>({}, P1, P2);
84
85 // clang-format off
86 constexpr Field() noexcept = default;
87 constexpr Field(Field const&) noexcept = default;
88 constexpr Field& operator=(Field const&) noexcept = default;
89 constexpr Field(Field&&) noexcept = default;
90 constexpr Field& operator=(Field&&) noexcept = default;
91 constexpr ~Field() noexcept = default;
92 // clang-format on
93
94 /// Constructs a new field with the given value.
95 template <typename... S>
96 requires std::constructible_from<T, S...>
97 constexpr Field(S&&... value) noexcept;
98
99 /// Assigns a new value to the field.
100 template <typename S>
101 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
102 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature)
103 constexpr Field& operator=(S&& value) noexcept;
104
105 /// Indicates if the field is optional, i.e., it can be NULL.
106 static constexpr auto IsOptional = detail::IsStdOptional<T>;
107
108 /// Indicates if the field is mandatory, i.e., it cannot be NULL.
109 static constexpr auto IsMandatory = !IsOptional;
110
111 /// Indicates if the field is a primary key.
112 static constexpr auto IsPrimaryKey = IsPrimaryKeyValue != PrimaryKey::No;
113
114 /// Indicates if this is a primary key, it also is auto-assigned by the client.
115 static constexpr auto IsAutoAssignPrimaryKey = IsPrimaryKeyValue == PrimaryKey::AutoAssign;
116
117 /// Indicates if this is a primary key, it also is auto-incremented by the database.
118 static constexpr auto IsAutoIncrementPrimaryKey = IsPrimaryKeyValue == PrimaryKey::ServerSideAutoIncrement;
119
120 /// Compares two fields for equality.
121 constexpr std::weak_ordering operator<=>(Field const& other) const noexcept;
122
123 /// Compares the field value with the given value for equality.
124 bool operator==(Field const& value) const noexcept = default;
125
126 /// Compares the field value with the given value for inequality.
127 bool operator!=(Field const& value) const noexcept = default;
128
129 /// Compares the field value with the given value for equality.
130 template <typename S>
131 requires std::convertible_to<S, T>
132 bool operator==(S const& value) const noexcept;
133
134 /// Compares the field value with the given value for inequality.
135 template <typename S>
136 requires std::convertible_to<S, T>
137 bool operator!=(S const& value) const noexcept;
138
139 /// Returns a string representation of the value, suitable for use in debugging and logging.
140 [[nodiscard]] std::string InspectValue() const;
141
142 /// Sets the modified state of the field.
143 constexpr void SetModified(bool value) noexcept;
144
145 /// Checks if the field has been modified.
146 [[nodiscard]] constexpr bool IsModified() const noexcept;
147
148 /// Returns the value of the field.
149 [[nodiscard]] constexpr T const& Value() const noexcept;
150
151 /// Returns a mutable reference to the value of the field.
152 ///
153 /// @note If the field value is changed through this method, it will not be automatically marked as modified.
154 [[nodiscard]] constexpr T& MutableValue() noexcept;
155
156 private:
157 ValueType _value {};
158 bool _modified { false };
159};
160
161// clang-format off
162namespace detail
163{
164
165template <typename T>
166struct IsAutoAssignPrimaryKeyField: std::false_type {};
167
168template <typename T, auto P>
169struct IsAutoAssignPrimaryKeyField<Field<T, PrimaryKey::AutoAssign, P>>: std::true_type {};
170
171template <typename T, auto P>
172struct IsAutoAssignPrimaryKeyField<Field<T, P, PrimaryKey::AutoAssign>>: std::true_type {};
173
174template <typename T>
175struct IsAutoIncrementPrimaryKeyField: std::false_type {};
176
177template <typename T, auto P>
178struct IsAutoIncrementPrimaryKeyField<Field<T, PrimaryKey::ServerSideAutoIncrement, P>>: std::true_type {};
179
180template <typename T, auto P>
181struct IsAutoIncrementPrimaryKeyField<Field<T, P, PrimaryKey::ServerSideAutoIncrement>>: std::true_type {};
182
183template <typename T>
184struct IsFieldType: std::false_type {};
185
186template <typename T, auto P1, auto P2>
187struct IsFieldType<Field<T, P1, P2>>: std::true_type {};
188
189} // namespace detail
190// clang-format on
191
192/// Tests if T is a Field<> that is a primary key.
193template <typename T>
194constexpr bool IsPrimaryKey =
195 detail::IsAutoAssignPrimaryKeyField<T>::value || detail::IsAutoIncrementPrimaryKeyField<T>::value;
196
197/// Requires that T satisfies to be a field with storage and is considered a primary key.
198template <typename T>
199constexpr bool IsAutoIncrementPrimaryKey = detail::IsAutoIncrementPrimaryKeyField<T>::value;
200
201template <typename T>
202constexpr bool IsField = detail::IsFieldType<std::remove_cvref_t<T>>::value;
203
204template <detail::FieldElementType T, auto P1, auto P2>
205template <typename... S>
206 requires std::constructible_from<T, S...>
207constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::Field(S&&... value) noexcept:
208 _value(std::forward<S>(value)...)
209{
210}
211
212template <detail::FieldElementType T, auto P1, auto P2>
213template <typename S>
214 requires std::constructible_from<T, S> && (!std::same_as<std::remove_cvref_t<S>, Field<T, P1, P2>>)
215constexpr LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>& Field<T, P1, P2>::operator=(S&& value) noexcept
216{
217 _value = std::forward<S>(value);
218 SetModified(true);
219 return *this;
220}
221
222template <detail::FieldElementType T, auto P1, auto P2>
223constexpr std::weak_ordering LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator<=>(Field const& other) const noexcept
224{
225 return _value <=> other._value;
226}
227
228template <detail::FieldElementType T, auto P1, auto P2>
229template <typename S>
230 requires std::convertible_to<S, T>
231inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator==(S const& value) const noexcept
232{
233 return _value == value;
234}
235
236template <detail::FieldElementType T, auto P1, auto P2>
237template <typename S>
238 requires std::convertible_to<S, T>
239inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator!=(S const& value) const noexcept
240{
241 return _value != value;
242}
243
244template <detail::FieldElementType T, auto P1, auto P2>
245inline LIGHTWEIGHT_FORCE_INLINE std::string Field<T, P1, P2>::InspectValue() const
246{
247 if constexpr (std::is_same_v<T, std::string>)
248 {
249 std::stringstream result;
250 result << std::quoted(_value, '\'');
251 return result.str();
252 }
253 else if constexpr (std::is_same_v<T, SqlText>)
254 {
255 std::stringstream result;
256 result << std::quoted(_value.value, '\'');
257 return result.str();
258 }
259 else if constexpr (std::is_same_v<T, SqlDate>)
260 return std::format("\'{}\'", _value.value);
261 else if constexpr (std::is_same_v<T, SqlTime>)
262 return std::format("\'{}\'", _value.value);
263 else if constexpr (std::is_same_v<T, SqlDateTime>)
264 return std::format("\'{}\'", _value.value());
265 else if constexpr (requires { _value.has_value(); })
266 {
267 if (_value.has_value())
268 return std::format("{}", _value.value());
269 else
270 return "NULL";
271 }
272 else
273 return std::format("{}", _value);
274}
275
276// ------------------------------------------------------------------------------------------------
277
278template <detail::FieldElementType T, auto P1, auto P2>
279constexpr LIGHTWEIGHT_FORCE_INLINE void Field<T, P1, P2>::SetModified(bool value) noexcept
280{
281 _modified = value;
282}
283
284template <detail::FieldElementType T, auto P1, auto P2>
285constexpr LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::IsModified() const noexcept
286{
287 return _modified;
288}
289
290template <detail::FieldElementType T, auto P1, auto P2>
291constexpr LIGHTWEIGHT_FORCE_INLINE T const& Field<T, P1, P2>::Value() const noexcept
292{
293 return _value;
294}
295
296template <detail::FieldElementType T, auto P1, auto P2>
297constexpr LIGHTWEIGHT_FORCE_INLINE T& Field<T, P1, P2>::MutableValue() noexcept
298{
299 return _value;
300}
301
302template <detail::FieldElementType T, auto P1, auto P2>
303struct SqlDataBinder<Field<T, P1, P2>>
304{
305 using ValueType = Field<T, P1, P2>;
306
307 static constexpr auto ColumnType = SqlDataBinder<T>::ColumnType;
308
309 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
310 SQLUSMALLINT column,
311 ValueType const& value,
313 {
314 return SqlDataBinder<T>::InputParameter(stmt, column, value.Value(), cb);
315 }
316
317 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
318 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
319 {
320 return SqlDataBinder<T>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
321 }
322
323 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
324 SQLHSTMT stmt, SQLUSMALLINT column, ValueType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
325 {
326 return SqlDataBinder<T>::GetColumn(stmt, column, &result->emplace(), indicator, cb);
327 }
328
329 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(ValueType const& value) noexcept
330 {
331 return value.InspectValue();
332 }
333};
334
335template <detail::FieldElementType T, auto P1, auto P2>
336struct std::formatter<Field<T, P1, P2>>: std::formatter<T>
337{
338 template <typename FormatContext>
339 auto format(Field<T, P1, P2> const& field, FormatContext& ctx)
340 {
341 return formatter<T>::format(field.InspectValue(), ctx);
342 }
343};
Represents a single column in a table.
Definition Field.hpp:79
constexpr T & MutableValue() noexcept
Definition Field.hpp:297
std::string InspectValue() const
Returns a string representation of the value, suitable for use in debugging and logging.
Definition Field.hpp:245
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:291
bool operator!=(S const &value) const noexcept
Compares the field value with the given value for inequality.
bool operator==(S const &value) const noexcept
Compares the field value with the given value for equality.
constexpr void SetModified(bool value) noexcept
Sets the modified state of the field.
Definition Field.hpp:279
constexpr Field(S &&... value) noexcept
Constructs a new field with the given value.
static constexpr auto IsPrimaryKey
Indicates if the field is a primary key.
Definition Field.hpp:112
static constexpr auto IsAutoIncrementPrimaryKey
Indicates if this is a primary key, it also is auto-incremented by the database.
Definition Field.hpp:118
constexpr std::weak_ordering operator<=>(Field const &other) const noexcept
Compares two fields for equality.
Definition Field.hpp:223
constexpr bool IsModified() const noexcept
Checks if the field has been modified.
Definition Field.hpp:285
bool operator==(Field const &value) const noexcept=default
Compares the field value with the given value for equality.
static constexpr auto IsMandatory
Indicates if the field is mandatory, i.e., it cannot be NULL.
Definition Field.hpp:109
bool operator!=(Field const &value) const noexcept=default
Compares the field value with the given value for inequality.
static constexpr auto IsAutoAssignPrimaryKey
Indicates if this is a primary key, it also is auto-assigned by the client.
Definition Field.hpp:115
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:106