relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
insert.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "core.hpp"
5#include "meta.hpp"
6#include "select.hpp"
7#include "value.hpp"
8
9#include <iostream>
10#include <memory>
11#include <optional>
12#include <ranges>
13#include <sstream>
14#include <string>
15#include <tuple>
16#include <utility>
17#include <vector>
18
19namespace relx::query {
20
24template <ColumnType Column, SqlExpr ValExpr>
25struct InsertItem {
27 ValExpr value;
28
29 // Constructor to ensure the InsertItem can be properly initialized
30 InsertItem(ColumnRef<Column> col, ValExpr val) : column(std::move(col)), value(std::move(val)) {}
31
32 std::string column_name() const { return column.column_name(); }
33
34 std::string value_sql() const { return value.to_sql(); }
35
36 std::vector<std::string> bind_params() const { return value.bind_params(); }
37};
38
45template <TableType Table, typename Columns = std::tuple<>, typename Values = std::tuple<>,
46 typename SelectStmt = std::nullopt_t, typename ReturningColumns = std::tuple<>>
48private:
49 Table table_;
50 Columns columns_;
51 Values values_;
52 SelectStmt select_;
53 ReturningColumns returning_columns_;
54
55 // Helper to convert a tuple of column references to column names for INSERT
56 std::string columns_to_sql() const {
57 std::stringstream ss;
58 ss << "(";
59 int i = 0;
60 std::apply(
61 [&](const auto&... cols) { ((ss << (i++ > 0 ? ", " : "") << cols.column_name()), ...); },
62 columns_);
63 ss << ")";
64 return ss.str();
65 }
66
67 // Helper to convert a tuple of values to SQL for a single row VALUES clause
68 template <typename ValueTuple>
69 std::string values_row_to_sql(const ValueTuple& value_tuple) const {
70 std::stringstream ss;
71 ss << "(";
72 int i = 0;
73 std::apply([&](const auto&... vals) { ((ss << (i++ > 0 ? ", " : "") << vals.to_sql()), ...); },
74 value_tuple);
75 ss << ")";
76 return ss.str();
77 }
78
79 // Helper to convert a tuple of value tuples to SQL for the VALUES clause
80 std::string values_to_sql() const {
81 std::stringstream ss;
82 ss << "VALUES ";
83 int i = 0;
84 std::apply(
85 [&](const auto&... value_tuples) {
86 ((ss << (i++ > 0 ? ", " : "") << values_row_to_sql(value_tuples)), ...);
87 },
88 values_);
89 return ss.str();
90 }
91
92 // Helper to collect bind parameters from a tuple of values
93 template <typename ValueTuple>
94 std::vector<std::string> values_row_bind_params(const ValueTuple& value_tuple) const {
95 std::vector<std::string> params;
96
97 std::apply(
98 [&](const auto&... vals) {
99 auto process_val = [&params](const auto& val) {
100 auto val_params = val.bind_params();
101 params.insert(params.end(), val_params.begin(), val_params.end());
102 };
103
104 (process_val(vals), ...);
105 },
106 value_tuple);
107
108 return params;
109 }
110
111 // Helper to collect bind parameters from a tuple of value tuples
112 std::vector<std::string> values_bind_params() const {
113 std::vector<std::string> params;
114
115 std::apply(
116 [&](const auto&... value_tuples) {
117 auto process_tuple = [&params, this](const auto& tuple) {
118 auto tuple_params = values_row_bind_params(tuple);
119 params.insert(params.end(), tuple_params.begin(), tuple_params.end());
120 };
121
122 (process_tuple(value_tuples), ...);
123 },
124 values_);
125
126 return params;
127 }
128
129 // Helper to convert the RETURNING clause to SQL
130 std::string returning_to_sql() const {
131 if constexpr (is_empty_tuple<ReturningColumns>()) {
132 return "";
133 } else {
134 std::stringstream ss;
135 ss << " RETURNING ";
136 int i = 0;
137 std::apply(
138 [&](const auto&... cols) { ((ss << (i++ > 0 ? ", " : "") << cols.to_sql()), ...); },
139 returning_columns_);
140 return ss.str();
141 }
142 }
143
144 // Helper to collect bind parameters from RETURNING clause
145 std::vector<std::string> returning_bind_params() const {
146 std::vector<std::string> params;
147
148 if constexpr (!is_empty_tuple<ReturningColumns>()) {
149 std::apply(
150 [&](const auto&... cols) {
151 auto process_col = [&params](const auto& col) {
152 auto col_params = col.bind_params();
153 params.insert(params.end(), col_params.begin(), col_params.end());
154 };
155
156 (process_col(cols), ...);
157 },
158 returning_columns_);
159 }
160
161 return params;
162 }
163
164public:
165 using table_type = Table;
166 using columns_type = Columns;
167 using values_type = Values;
168 using select_type = SelectStmt;
169 using returning_columns_type = ReturningColumns;
170
177 explicit InsertQuery(Table table, Columns columns = {}, Values values = {},
178 SelectStmt select = std::nullopt, ReturningColumns returning_columns = {})
179 : table_(std::move(table)), columns_(std::move(columns)), values_(std::move(values)),
180 select_(std::move(select)), returning_columns_(std::move(returning_columns)) {}
181
184 std::string to_sql() const {
185 std::stringstream ss;
186 ss << "INSERT INTO " << table_.table_name;
187
188 // Add columns clause if columns are specified
189 if constexpr (!is_empty_tuple<Columns>()) {
190 ss << " " << columns_to_sql();
191 }
192
193 // Handle different types of INSERT statements
194
195 // INSERT ... VALUES ...
196 if constexpr (!is_empty_tuple<Values>() && std::is_same_v<SelectStmt, std::nullopt_t>) {
197 ss << " " << values_to_sql();
198 }
199 // INSERT ... SELECT ...
200 else if constexpr (!std::is_same_v<SelectStmt, std::nullopt_t>) {
201 if (select_.has_value()) {
202 ss << " " << select_.value().to_sql();
203 }
204 }
205
206 // Add RETURNING clause if specified
207 ss << returning_to_sql();
208
209 return ss.str();
210 }
211
214 std::vector<std::string> bind_params() const {
215 std::vector<std::string> params;
216
217 // INSERT ... VALUES ...
218 if constexpr (!is_empty_tuple<Values>() && std::is_same_v<SelectStmt, std::nullopt_t>) {
219 auto values_params = values_bind_params();
220 params.insert(params.end(), values_params.begin(), values_params.end());
221 }
222 // INSERT ... SELECT ...
223 else if constexpr (!std::is_same_v<SelectStmt, std::nullopt_t>) {
224 if (select_.has_value()) {
225 auto select_params = select_.value().bind_params();
226 params.insert(params.end(), select_params.begin(), select_params.end());
227 }
228 }
229
230 // Add RETURNING bind parameters
231 auto returning_params = returning_bind_params();
232 params.insert(params.end(), returning_params.begin(), returning_params.end());
233
234 return params;
235 }
236
241 template <ColumnType... Cols>
242 auto columns(const Cols&... cols) const {
243 using NewColumns = std::tuple<ColumnRef<Cols>...>;
244 auto column_refs = std::make_tuple(ColumnRef<Cols>(cols)...);
245
247 table_, std::move(column_refs), values_, select_, returning_columns_);
248 }
249
254 template <typename... Args>
255 auto values(Args&&... args) const {
256 // Helper to convert arguments to SqlExpr if they're not already
257 auto to_expr = [](auto&& arg) {
258 if constexpr (SqlExpr<std::remove_cvref_t<decltype(arg)>>) {
259 return std::forward<decltype(arg)>(arg);
260 } else {
261 return val(std::forward<decltype(arg)>(arg));
262 }
263 };
264
265 using ValueTuple = std::tuple<decltype(to_expr(std::declval<Args>()))...>;
266 auto value_tuple = std::make_tuple(to_expr(std::forward<Args>(args))...);
267
268 // If we already have value tuples, add this one to them
269 if constexpr (!is_empty_tuple<Values>()) {
270 auto new_values = std::tuple_cat(values_, std::make_tuple(value_tuple));
271
273 table_, columns_, std::move(new_values), select_, returning_columns_);
274 }
275 // If this is the first value tuple, create a new tuple
276 else {
277 using NewValues = std::tuple<ValueTuple>;
278 auto new_values = std::make_tuple(value_tuple);
279
281 table_, columns_, std::move(new_values), select_, returning_columns_);
282 }
283 }
284
289 template <typename Select>
290 requires SqlExpr<Select>
291 auto select(const Select& select) const {
293 table_, columns_, values_, std::optional<Select>(select), returning_columns_);
294 }
295
300 template <typename... Args>
301 auto returning(const Args&... args) const {
302 // Helper to convert columns to ColumnRef expressions if they're not already SqlExpr
303 auto to_expr = [](const auto& arg) {
304 if constexpr (SqlExpr<std::remove_cvref_t<decltype(arg)>>) {
305 return arg;
306 } else if constexpr (ColumnType<std::remove_cvref_t<decltype(arg)>>) {
307 return column_ref(arg);
308 } else {
309 static_assert(SqlExpr<std::remove_cvref_t<decltype(arg)>> ||
310 ColumnType<std::remove_cvref_t<decltype(arg)>>,
311 "Arguments to returning() must be either columns or SQL expressions");
312 // This line is never reached, it's just to make the compiler happy
313 return arg;
314 }
315 };
316
317 using ReturningTuple = std::tuple<decltype(to_expr(std::declval<Args>()))...>;
318 auto returning_tuple = std::make_tuple(to_expr(args)...);
319
321 table_, columns_, values_, select_, std::move(returning_tuple));
322 }
323};
324
329template <TableType Table>
330auto insert_into(const Table& table) {
331 return InsertQuery<Table>(table);
332}
333
334} // namespace relx::query
Column reference expression.
Base INSERT query builder.
Definition insert.hpp:47
auto values(Args &&... args) const
Add a row of values to insert.
Definition insert.hpp:255
std::vector< std::string > bind_params() const
Get the bind parameters for this INSERT query.
Definition insert.hpp:214
ReturningColumns returning_columns_type
Definition insert.hpp:169
auto columns(const Cols &... cols) const
Specify columns to insert into.
Definition insert.hpp:242
InsertQuery(Table table, Columns columns={}, Values values={}, SelectStmt select=std::nullopt, ReturningColumns returning_columns={})
Constructor for the INSERT query builder.
Definition insert.hpp:177
auto select(const Select &select) const
Set a SELECT query to use for INSERT ... SELECT statements.
Definition insert.hpp:291
auto returning(const Args &... args) const
Specify columns to return after insertion.
Definition insert.hpp:301
std::string to_sql() const
Generate the SQL for this INSERT query.
Definition insert.hpp:184
Represents a column in a database table.
Definition column.hpp:217
Concept for column types.
Definition core.hpp:40
Concept for SQL expression components.
Definition core.hpp:29
auto column_ref(const Column &col)
Create a column reference expression.
auto insert_into(const Table &table)
Create an INSERT query for the specified table.
Definition insert.hpp:330
auto to_expr(const C &col, std::string_view table_name="")
Helper to wrap a schema column in a SQL expression.
auto select(const Args &... args)
Create a column reference from a member pointer without requiring a table instance.
Definition select.hpp:501
auto val(const char *str)
Helper to create a value expression from a string literal.
Definition value.hpp:127
STL namespace.
Represents a single column-value pair for an INSERT statement.
Definition insert.hpp:25
std::string value_sql() const
Definition insert.hpp:34
std::vector< std::string > bind_params() const
Definition insert.hpp:36
ColumnRef< Column > column
Definition insert.hpp:26
InsertItem(ColumnRef< Column > col, ValExpr val)
Definition insert.hpp:30
std::string column_name() const
Definition insert.hpp:32