relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
column.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "core.hpp"
4#include "fixed_string.hpp"
5
6#include <optional>
7#include <string_view>
8#include <tuple>
9#include <type_traits>
10
11namespace relx::schema {
12
14struct unique {
15 static std::string to_sql() { return " UNIQUE"; }
16};
17
20 static std::string to_sql() { return " PRIMARY KEY"; }
21};
22
25 static std::string to_sql() {
26 // SQLite syntax
27 return " AUTOINCREMENT";
28 }
29};
30
32template <typename T>
33concept ValidIdentityType = std::is_integral_v<T> && !std::is_same_v<T, bool>;
34
41template <auto Start = 1, auto Increment = 1,
42 auto MinValue = std::numeric_limits<decltype(Start)>::min(),
43 auto MaxValue = std::numeric_limits<decltype(Start)>::max(), bool Cycle = false>
44 requires ValidIdentityType<decltype(Start)> && ValidIdentityType<decltype(Increment)> &&
45 ValidIdentityType<decltype(MinValue)> && ValidIdentityType<decltype(MaxValue)>
46struct identity {
47 static constexpr auto start = Start;
48 static constexpr auto increment = Increment;
49 static constexpr auto min_value = MinValue;
50 static constexpr auto max_value = MaxValue;
51 static constexpr bool cycle = Cycle;
52
53 static std::string to_sql() {
54 std::string result = " GENERATED ALWAYS AS IDENTITY";
55
56 // Add options if they differ from defaults
57 if constexpr (start != 1 || increment != 1 ||
58 min_value != std::numeric_limits<decltype(start)>::min() ||
59 max_value != std::numeric_limits<decltype(start)>::max() || cycle) {
60 result += " (";
61
62 // Add start value if not default
63 if constexpr (start != 1) {
64 result += "START WITH " + std::to_string(start);
65 }
66
67 // Add increment if not default
68 if constexpr (increment != 1) {
69 if constexpr (start != 1) {
70 result += " ";
71 }
72 result += "INCREMENT BY " + std::to_string(increment);
73 }
74
75 // Add min value if not default
76 if constexpr (min_value != std::numeric_limits<decltype(start)>::min()) {
77 if constexpr (start != 1 || increment != 1) {
78 result += " ";
79 }
80 result += "MINVALUE " + std::to_string(min_value);
81 }
82
83 // Add max value if not default
84 if constexpr (max_value != std::numeric_limits<decltype(start)>::max()) {
85 if constexpr (start != 1 || increment != 1 ||
86 min_value != std::numeric_limits<decltype(start)>::min()) {
87 result += " ";
88 }
89 result += "MAXVALUE " + std::to_string(max_value);
90 }
91
92 // Add cycle option if true
93 if constexpr (cycle) {
94 if constexpr (start != 1 || increment != 1 ||
95 min_value != std::numeric_limits<decltype(start)>::min() ||
96 max_value != std::numeric_limits<decltype(start)>::max()) {
97 result += " ";
98 }
99 result += "CYCLE";
100 }
101
102 result += ")";
103 }
104
105 return result;
106 }
107};
108
110template <fixed_string Expr>
111struct check {
112 static constexpr auto expr = Expr;
113
114 static std::string to_sql() { return " CHECK(" + std::string(std::string_view(expr)) + ")"; }
115};
116
118template <fixed_string Table, fixed_string Column>
120 static constexpr auto table = Table;
121 static constexpr auto column = Column;
122
123 static std::string to_sql() {
124 return " REFERENCES " + std::string(std::string_view(table)) + "(" +
125 std::string(std::string_view(column)) + ")";
126 }
127};
128
130template <fixed_string Action>
131struct on_delete {
132 static constexpr auto action = Action;
133
134 static std::string to_sql() { return " ON DELETE " + std::string(std::string_view(action)); }
135};
136
138template <fixed_string Action>
139struct on_update {
140 static constexpr auto action = Action;
141
142 static std::string to_sql() { return " ON UPDATE " + std::string(std::string_view(action)); }
143};
144
146template <auto Value>
148 using value_type = decltype(Value);
149
150 static constexpr value_type value = Value;
151
152 static std::string to_sql() {
153 if constexpr (std::is_same_v<value_type, bool>) {
154 // For boolean values, use true/false in SQL
155 return " DEFAULT " + std::string(value ? "true" : "false");
156 } else if constexpr (std::is_integral_v<value_type> || std::is_floating_point_v<value_type>) {
157 // For numeric types, convert to string
158 return " DEFAULT " + std::to_string(value);
159 } else {
160 // Fall back to generic string conversion
161 return " DEFAULT " + std::to_string(value);
162 }
163 }
164};
165
167template <fixed_string Value, bool IsLiteral = false>
169 static constexpr auto value = Value;
170
171 static std::string to_sql() {
172 if constexpr (IsLiteral) {
173 // SQL function or literal that should not be quoted
174 return " DEFAULT " + std::string(std::string_view(value));
175 } else {
176 // Normal string that should be quoted
177 return " DEFAULT '" + std::string(std::string_view(value)) + "'";
178 }
179 }
180};
181
184 static std::string to_sql() { return " DEFAULT NULL"; }
185};
186
188template <typename... Modifiers>
189static std::string apply_modifiers() {
190 std::string result;
191 [[maybe_unused]] auto _ = (result += ... += Modifiers::to_sql()); // supress unused warning
192 return result;
193}
194
195// Type trait to detect default_value specialization
196template <typename TypeParam>
197struct is_default_value_specialization : std::false_type {};
198
199template <auto Value>
200struct is_default_value_specialization<default_value<Value>> : std::true_type {
201 using value_type = decltype(Value);
202};
203
204// Type trait to detect string_default specialization
205template <typename TypeParam>
206struct is_string_default_specialization : std::false_type {};
207
208template <fixed_string Value, bool IsLiteral>
209struct is_string_default_specialization<string_default<Value, IsLiteral>> : std::true_type {};
210
216template <typename TableT, fixed_string Name, typename T, typename... Modifiers>
217class column {
218public:
220 using table_type = TableT;
221
223 using value_type = T;
224
226 static constexpr auto name = Name;
227
230
232 static constexpr bool nullable = column_traits<T>::nullable;
233
236 std::string sql_definition() const {
237 std::string result = std::string(std::string_view(name)) + " " +
238 std::string(std::string_view(sql_type));
239
240 // Add NOT NULL if not nullable
241 if (!nullable) {
242 result += " NOT NULL";
243 }
244
245 // Apply all modifiers
246 result += apply_modifiers<Modifiers...>();
247
248 return result;
249 }
250
254 static std::string to_sql_string(const T& value) {
256 }
257
261 static T from_sql_string(const std::string& sql_str) {
262 return column_traits<T>::from_sql_string(sql_str);
263 }
264
267 std::optional<T> get_default_value() const { return find_default_value<T, Modifiers...>(); }
268
272 template <typename PatternType>
273 requires std::convertible_to<PatternType, std::string>
274 auto like(PatternType&& pattern) const;
275
278 auto is_null() const;
279
282 auto is_not_null() const;
283
284private:
285 // Helper to find default value in the modifiers
286 template <typename ValueT, typename First, typename... Rest>
287 static std::optional<ValueT> find_default_value() {
288 if constexpr (is_default_value_type<First, ValueT>()) {
289 return extract_default_value<First, ValueT>();
290 } else if constexpr (sizeof...(Rest) > 0) {
291 return find_default_value<ValueT, Rest...>();
292 } else {
293 return std::nullopt;
294 }
295 }
296
297 template <typename ValueT>
298 static std::optional<ValueT> find_default_value() {
299 return std::nullopt;
300 }
301
302 // Check if a type is a default value type
303 template <typename Mod, typename ValueT>
304 static constexpr bool is_default_value_type() {
305 if constexpr (std::is_same_v<Mod, null_default>) {
307 } else if constexpr (is_default_value_specialization<Mod>::value) {
308 using DefaultValueType = typename is_default_value_specialization<Mod>::value_type;
309 return std::is_same_v<DefaultValueType, ValueT> ||
310 std::is_convertible_v<DefaultValueType, ValueT>;
311 } else if constexpr (is_string_default_specialization<Mod>::value) {
312 if constexpr (std::is_same_v<ValueT, std::string> ||
313 std::is_convertible_v<std::string, ValueT> ||
314 std::is_constructible_v<ValueT, std::string_view> ||
315 std::is_constructible_v<ValueT, const char*>) {
316 return true;
317 } else {
318 return false;
319 }
320 } else {
321 return false;
322 }
323 }
324
325 // Extract the default value from a modifier
326 template <typename Mod, typename ValueT>
327 static std::optional<ValueT> extract_default_value() {
328 if constexpr (std::is_same_v<Mod, null_default>) {
329 if constexpr (column_traits<ValueT>::nullable) {
330 return std::nullopt;
331 }
332 } else if constexpr (is_default_value_specialization<Mod>::value) {
333 return static_cast<ValueT>(Mod::value);
334 } else if constexpr (is_string_default_specialization<Mod>::value) {
335 if constexpr (std::is_constructible_v<ValueT, std::string_view>) {
336 return ValueT(std::string_view(Mod::value));
337 } else if constexpr (std::is_constructible_v<ValueT, std::string>) {
338 return ValueT(std::string(std::string_view(Mod::value)));
339 } else if constexpr (std::is_constructible_v<ValueT, const char*>) {
340 return ValueT(Mod::value.data());
341 }
342 }
343 return std::nullopt;
344 }
345};
346
347// Specialization for std::optional columns (nullable)
348template <typename TableT, fixed_string Name, typename T, typename... Modifiers>
349class column<TableT, Name, std::optional<T>, Modifiers...> {
350public:
352 using table_type = TableT;
353
354 using value_type = std::optional<T>;
355 using base_type = T;
356
357 static constexpr auto name = Name;
359 static constexpr bool nullable = true;
360
361 std::string sql_definition() const {
362 std::string result = std::string(std::string_view(name)) + " " +
363 std::string(std::string_view(sql_type));
364
365 // No NOT NULL constraint for optional columns
366
367 // Apply all modifiers
368 result += apply_modifiers<Modifiers...>();
369
370 return result;
371 }
372
373 static std::string to_sql_string(const std::optional<T>& value) {
374 if (value.has_value()) {
375 return column_traits<T>::to_sql_string(*value);
376 }
377 return "NULL";
378 }
379
380 static std::optional<T> from_sql_string(const std::string& sql_str) {
381 if (sql_str == "NULL") {
382 return std::nullopt;
383 }
384 return column_traits<T>::from_sql_string(sql_str);
385 }
386
389 std::optional<std::optional<T>> get_default_value() const {
390 return find_default_value<std::optional<T>, Modifiers...>();
391 }
392
396 template <typename PatternType>
397 requires std::convertible_to<PatternType, std::string>
398 auto like(PatternType&& pattern) const;
399
402 auto is_null() const;
403
406 auto is_not_null() const;
407
408private:
409 // Helper to find default value in the modifiers
410 template <typename ValueT, typename First, typename... Rest>
411 static std::optional<ValueT> find_default_value() {
412 if constexpr (is_default_value_type<First, ValueT>()) {
413 return extract_default_value<First, ValueT>();
414 } else if constexpr (sizeof...(Rest) > 0) {
415 return find_default_value<ValueT, Rest...>();
416 } else {
417 return std::nullopt;
418 }
419 }
420
421 template <typename ValueT>
422 static std::optional<ValueT> find_default_value() {
423 return std::nullopt;
424 }
425
426 // Check if a type is a default value type
427 template <typename Mod, typename ValueT>
428 static constexpr bool is_default_value_type() {
429 if constexpr (std::is_same_v<Mod, null_default>) {
430 return true; // null_default is valid for std::optional<T>
431 } else if constexpr (is_default_value_specialization<Mod>::value) {
432 using DefaultValueType = typename is_default_value_specialization<Mod>::value_type;
433 using OptionalBaseType = typename ValueT::value_type;
434 return std::is_same_v<DefaultValueType, OptionalBaseType> ||
435 std::is_convertible_v<DefaultValueType, OptionalBaseType>;
436 } else if constexpr (is_string_default_specialization<Mod>::value) {
437 using OptionalBaseType = typename ValueT::value_type;
438 return std::is_convertible_v<std::string_view, OptionalBaseType>;
439 } else {
440 return false;
441 }
442 }
443
444 // Extract the default value from a modifier
445 template <typename Mod, typename ValueT>
446 static std::optional<ValueT> extract_default_value() {
447 if constexpr (std::is_same_v<Mod, null_default>) {
448 if constexpr (column_traits<ValueT>::nullable) {
449 return std::nullopt;
450 }
451 } else if constexpr (is_default_value_specialization<Mod>::value) {
452 return static_cast<ValueT>(Mod::value);
453 } else if constexpr (is_string_default_specialization<Mod>::value) {
454 if constexpr (std::is_constructible_v<ValueT, std::string_view>) {
455 return ValueT(std::string_view(Mod::value));
456 } else if constexpr (std::is_constructible_v<ValueT, std::string>) {
457 return ValueT(std::string(std::string_view(Mod::value)));
458 } else if constexpr (std::is_constructible_v<ValueT, const char*>) {
459 return ValueT(Mod::value.data());
460 }
461 }
462 return std::nullopt;
463 }
464};
465
466} // namespace relx::schema
TableT table_type
The table type this column belongs to.
Definition column.hpp:352
static std::optional< T > from_sql_string(const std::string &sql_str)
Definition column.hpp:380
std::optional< std::optional< T > > get_default_value() const
Get the default value if set.
Definition column.hpp:389
static std::string to_sql_string(const std::optional< T > &value)
Definition column.hpp:373
Represents a column in a database table.
Definition column.hpp:217
static constexpr auto name
The column name.
Definition column.hpp:226
std::optional< T > get_default_value() const
Get the default value if set.
Definition column.hpp:267
auto is_null() const
Create an IS NULL condition for this column.
std::string sql_definition() const
Get the SQL definition of this column.
Definition column.hpp:236
auto is_not_null() const
Create an IS NOT NULL condition for this column.
TableT table_type
The table type this column belongs to.
Definition column.hpp:220
static std::string to_sql_string(const T &value)
Convert a C++ value to SQL string.
Definition column.hpp:254
static T from_sql_string(const std::string &sql_str)
Parse a SQL string to C++ value.
Definition column.hpp:261
T value_type
The C++ type of the column.
Definition column.hpp:223
static constexpr bool nullable
Flag indicating if the column can be NULL.
Definition column.hpp:232
static constexpr auto sql_type
The SQL type of the column.
Definition column.hpp:229
Concept for valid identity types.
Definition column.hpp:33
static std::string apply_modifiers()
Helper to apply all column modifiers to a SQL definition.
Definition column.hpp:189
STL namespace.
AUTOINCREMENT constraint.
Definition column.hpp:24
static std::string to_sql()
Definition column.hpp:25
CHECK constraint.
Definition column.hpp:111
static std::string to_sql()
Definition column.hpp:114
Contains schema definition components.
Definition core.hpp:18
static constexpr bool nullable
Whether this type can be NULL.
Definition core.hpp:23
static T from_sql_string(const std::string &value)
Parse a SQL string representation to a C++ value.
static std::string to_sql_string(const T &value)
Convert a C++ value to a SQL string representation.
DEFAULT value for non-string values.
Definition column.hpp:147
decltype(Value) value_type
Definition column.hpp:148
static std::string to_sql()
Definition column.hpp:152
Compile-time string type that can be used as template non-type parameter in C++20.
IDENTITY constraint with configurable options.
Definition column.hpp:46
static std::string to_sql()
Definition column.hpp:53
NULL default for optional types.
Definition column.hpp:183
static std::string to_sql()
Definition column.hpp:184
ON DELETE action for foreign keys.
Definition column.hpp:131
static std::string to_sql()
Definition column.hpp:134
ON UPDATE action for foreign keys.
Definition column.hpp:139
static std::string to_sql()
Definition column.hpp:142
PRIMARY KEY constraint.
Definition column.hpp:19
static std::string to_sql()
Definition column.hpp:20
REFERENCES constraint for foreign keys.
Definition column.hpp:119
static std::string to_sql()
Definition column.hpp:123
DEFAULT value for string literals.
Definition column.hpp:168
static std::string to_sql()
Definition column.hpp:171
UNIQUE constraint.
Definition column.hpp:14
static std::string to_sql()
Definition column.hpp:15