relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
foreign_key.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../utils/error_handling.hpp"
4#include "column.hpp"
5#include "meta.hpp"
6
7#include <array>
8#include <string_view>
9#include <type_traits>
10
11namespace relx::schema {
12
14// Keep the names lowercase to match the lowercase table api
15// NOLINTNEXTLINE(readability-identifier-naming)
17
21constexpr std::string_view reference_action_to_string(reference_action action) {
22 switch (action) {
24 return "CASCADE";
26 return "RESTRICT";
28 return "SET NULL";
30 return "SET DEFAULT";
32 return "NO ACTION";
33 default:
34 return "NO ACTION";
35 }
36}
37
41template <auto LocalColumnPtr, auto ReferencedColumnPtr>
43public:
46 : on_delete_(reference_action::no_action), on_update_(reference_action::no_action) {}
47
53
56 std::string sql_definition() const {
57 // Extract column information
58 using local_table_type = typename member_pointer_class<decltype(LocalColumnPtr)>::type;
59 using local_column_type = typename member_pointer_type<decltype(LocalColumnPtr)>::type;
60
61 using ref_table_type = typename member_pointer_class<decltype(ReferencedColumnPtr)>::type;
62 using ref_column_type = typename member_pointer_type<decltype(ReferencedColumnPtr)>::type;
63
64 // Get the table name directly
65 std::string ref_table_name = std::string(ref_table_type::table_name);
66
67 // Generate the SQL using the static members
68 std::string result = "FOREIGN KEY (" + std::string(local_column_type::name) + ") ";
69 result += "REFERENCES " + ref_table_name + "(";
70 result += std::string(ref_column_type::name) + ")";
71
72 // Add ON DELETE and ON UPDATE clauses
73 if (on_delete_ != reference_action::no_action) {
74 result += " ON DELETE " + std::string(reference_action_to_string(on_delete_));
75 }
76
77 if (on_update_ != reference_action::no_action) {
78 result += " ON UPDATE " + std::string(reference_action_to_string(on_update_));
79 }
80
81 return result;
82 }
83
84private:
87};
88
92template <auto... ColumnPtrs>
94public:
97 : on_delete_(reference_action::no_action), on_update_(reference_action::no_action) {}
98
104
107 std::string sql_definition() const {
108 // Generate the SQL
109 std::string result = "FOREIGN KEY (";
110
111 // Add local columns (first half of the parameter pack)
112 std::string local_names;
113 get_column_names<0, sizeof...(ColumnPtrs) / 2>(local_names);
114 result += local_names;
115
116 // Add reference columns (second half of the parameter pack)
117 result += ") REFERENCES ";
118 std::string ref_table_name = get_referenced_table_name();
119 result += ref_table_name + "(";
120
121 std::string ref_names;
122 get_column_names<sizeof...(ColumnPtrs) / 2, sizeof...(ColumnPtrs)>(ref_names);
123 result += ref_names;
124
125 result += ")";
126
127 // Add ON DELETE and ON UPDATE clauses
128 if (on_delete_ != reference_action::no_action) {
129 result += " ON DELETE " + std::string(reference_action_to_string(on_delete_));
130 }
131
132 if (on_update_ != reference_action::no_action) {
133 result += " ON UPDATE " + std::string(reference_action_to_string(on_update_));
134 }
135
136 return result;
137 }
138
139private:
142
143 // Helper to get comma-separated column names for a range of parameters
144 template <size_t Start, size_t End>
145 void get_column_names(std::string& names) const {
146 get_column_names_impl<Start, End, ColumnPtrs...>(names);
147 }
148
149 // Implementation of get_column_names that handles the parameter pack
150 template <size_t Start, size_t End, auto FirstColumnPtr, auto... RestColumnPtrs>
151 void get_column_names_impl(std::string& names) const {
152 if constexpr (Start == 0) {
153 // First column, start building the name list
154 using column_type = typename member_pointer_type<decltype(FirstColumnPtr)>::type;
155 names = std::string(column_type::name);
156
157 // Process rest of the columns in range
158 if constexpr (sizeof...(RestColumnPtrs) > 0 && 1 < End) {
159 get_column_names_impl<0, End - 1, RestColumnPtrs...>(names, true);
160 }
161 } else {
162 // Skip this column and continue
163 get_column_names_impl<Start - 1, End - 1, RestColumnPtrs...>(names);
164 }
165 }
166
167 // Overload for appending to existing name list
168 template <size_t Start, size_t End, auto FirstColumnPtr, auto... RestColumnPtrs>
169 void get_column_names_impl(std::string& names, bool append) const {
170 if (append) {
171 using column_type = typename member_pointer_type<decltype(FirstColumnPtr)>::type;
172 names += ", " + std::string(column_type::name);
173 }
174
175 // Process rest of the columns in range
176 if constexpr (sizeof...(RestColumnPtrs) > 0 && 1 < End) {
177 get_column_names_impl<0, End - 1, RestColumnPtrs...>(names, append);
178 }
179 }
180
181 // Base case for empty pack
182 template <size_t Start, size_t End>
183 void get_column_names_impl(std::string& names) const {
184 // Do nothing for empty pack
185 }
186
187 template <size_t Start, size_t End>
188 void get_column_names_impl(std::string& names, bool append) const {
189 // Do nothing for empty pack
190 }
191
192 // Helper to get the referenced table name (from the first referenced column)
193 std::string get_referenced_table_name() const {
194 // Using template specialization to get the right column
195 return get_ref_table_name_impl<sizeof...(ColumnPtrs) / 2, ColumnPtrs...>();
196 }
197
198 // Implementation to get the referenced table name
199 template <size_t Index, auto FirstColumnPtr, auto... RestColumnPtrs>
200 std::string get_ref_table_name_impl() const {
201 if constexpr (Index == 0) {
202 // This is the first referenced column
203 using table_type = typename member_pointer_class<decltype(FirstColumnPtr)>::type;
204 return std::string(table_type::table_name);
205 } else {
206 // Skip this column and continue
207 return get_ref_table_name_impl<Index - 1, RestColumnPtrs...>();
208 }
209 }
210
211 // Base case for empty pack
212 template <size_t Index>
213 std::string get_ref_table_name_impl() const {
214 // Static assert to make sure template parameters are correct
215 static_assert(Index == 0,
216 "Invalid foreign key template parameters: could not determine referenced table");
217 return ""; // This line will never be reached due to static_assert
218 }
219
220 // Helper to extract the class type from a member pointer
221 template <typename T>
222 struct member_pointer_class;
223
224 template <typename C, typename T>
225 struct member_pointer_class<T C::*> {
226 using type = C;
227 };
228
229 // Helper to extract the member type from a member pointer
230 template <typename T>
231 struct member_pointer_type;
232
233 template <typename C, typename T>
234 struct member_pointer_type<T C::*> {
235 using type = T;
236 };
237};
238
239// Helper type alias for splitting column pointers into local and referenced parts
240// This preprocesses the arguments to make sure the two packs are equal length
241template <typename, typename>
242struct fk_impl;
243
244template <auto... LocalColumns, auto... ReferencedColumns>
245struct fk_impl<
246 std::tuple<std::integral_constant<decltype(LocalColumns), LocalColumns>...>,
247 std::tuple<std::integral_constant<decltype(ReferencedColumns), ReferencedColumns>...>> {
248 using type = std::conditional_t<sizeof...(LocalColumns) == 1,
249 foreign_key<(LocalColumns, ...), (ReferencedColumns, ...)>,
250 composite_foreign_key<LocalColumns..., ReferencedColumns...>>;
251};
252
253// Helper for getting pack element by index
254template <std::size_t N, auto... Args>
256
257// Base case: first element
258template <auto First, auto... Rest>
259struct pack_element<0, First, Rest...> {
260 static constexpr auto value = First;
261};
262
263// Recursive case: Nth element
264template <std::size_t N, auto First, auto... Rest>
265struct pack_element<N, First, Rest...> {
266 static constexpr auto value = pack_element<N - 1, Rest...>::value;
267};
268
273template <auto... Args>
274auto make_fk() {
275 constexpr std::size_t total_args = sizeof...(Args);
276 constexpr std::size_t half_size = total_args / 2;
277
278 static_assert(total_args % 2 == 0, "Number of arguments must be even");
279
280 if constexpr (half_size == 1) {
281 // Single column foreign key - extract first and second arguments
282 constexpr auto first_arg = pack_element<0, Args...>::value;
283 constexpr auto second_arg = pack_element<1, Args...>::value;
284
286 } else {
287 // Composite foreign key
288 return composite_foreign_key<Args...>();
289 }
290}
291
297template <auto... Args>
299 constexpr std::size_t total_args = sizeof...(Args);
300 constexpr std::size_t half_size = total_args / 2;
301
302 static_assert(total_args % 2 == 0, "Number of arguments must be even");
303
304 if constexpr (half_size == 1) {
305 // Single column foreign key - extract first and second arguments
306 constexpr auto first_arg = pack_element<0, Args...>::value;
307 constexpr auto second_arg = pack_element<1, Args...>::value;
308
310 } else {
311 // Composite foreign key
312 return composite_foreign_key<Args...>(on_delete, on_update);
313 }
314}
315
318template <auto... Args>
319using fk = decltype(make_fk<Args...>());
320
321} // namespace relx::schema
Represents a composite foreign key constraint with multiple columns.
std::string sql_definition() const
Get SQL definition for the composite FOREIGN KEY constraint.
composite_foreign_key()
Default constructor.
composite_foreign_key(reference_action on_delete, reference_action on_update)
Constructor with custom actions.
Represents a foreign key constraint on a table.
foreign_key(reference_action on_delete, reference_action on_update)
Constructor with custom actions.
std::string sql_definition() const
Get SQL definition for the FOREIGN KEY constraint.
foreign_key()
Default constructor.
decltype(make_fk< Args... >()) fk
Type alias for foreign key using the make_fk helper.
constexpr std::string_view reference_action_to_string(reference_action action)
Convert reference action to SQL string.
reference_action
Foreign key actions.
auto make_fk()
Helper function to create a foreign key.
STL namespace.
std::conditional_t< sizeof...(LocalColumns)==1, foreign_key<(LocalColumns,...),(ReferencedColumns,...)>, composite_foreign_key< LocalColumns..., ReferencedColumns... > > type
Helper to extract the class type from a member pointer.
Definition meta.hpp:10
Helper to extract the member type from a member pointer.
Definition meta.hpp:28
ON DELETE action for foreign keys.
Definition column.hpp:131
ON UPDATE action for foreign keys.
Definition column.hpp:139