3#include "../query/core.hpp"
4#include "../query/meta.hpp"
5#include "../schema/core.hpp"
6#include "../schema/table.hpp"
36template <
typename T,
typename C>
42template <auto MemberPtr>
45 using ColumnType = std::remove_reference_t<decltype(std::declval<Class>().*MemberPtr)>;
46 return ColumnType::column_name;
50template <
typename Table,
typename ColumnMemberPtr>
53 std::remove_reference_t<decltype(std::declval<Table>().*std::declval<ColumnMemberPtr>())>;
54 using type =
typename column_type::value_type;
57template <
typename Table,
typename ColumnMemberPtr>
61template <
typename Table,
typename ColumnMemberPtr>
63 using ColumnType = std::remove_reference_t<decltype(std::declval<Table>().*ptr)>;
64 return std::string(ColumnType::name);
71 explicit Cell(std::string value) : value_(
std::move(value)) {}
74 bool is_null()
const {
return value_ ==
"NULL"; }
77 const std::string&
raw_value()
const {
return value_; }
95 if constexpr (is_optional_v<T>) {
96 return T{std::nullopt};
98 return std::unexpected(
ResultError{
"Cannot convert NULL to non-optional type"});
102 if constexpr (std::is_same_v<T, bool>) {
103 const auto lower = to_lower(value_);
106 if (lower ==
"true") {
109 if (lower ==
"false") {
118 if (allow_numeric_bools) {
128 return std::unexpected(
ResultError{std::string(
"Cannot convert '") + value_ +
129 "' to boolean: not a boolean value"});
130 }
else if constexpr (std::is_same_v<T, int> || std::is_same_v<T, long> ||
131 std::is_same_v<T, long long>) {
133 const auto lower = to_lower(value_);
134 if (lower ==
"true" || lower ==
"false") {
135 return std::unexpected(
ResultError{
"Cannot convert boolean value to integer type"});
140 if (!is_valid_integer(value_)) {
141 return std::unexpected(
ResultError{
"Cannot convert to integer: invalid format"});
144 if constexpr (std::is_same_v<T, int>) {
145 return std::stoi(value_);
146 }
else if constexpr (std::is_same_v<T, long>) {
147 return std::stol(value_);
148 }
else if constexpr (std::is_same_v<T, long long>) {
149 return std::stoll(value_);
151 }
catch (
const std::exception& e) {
152 return std::unexpected(
ResultError{std::string(
"Error parsing cell value '") + value_ +
153 "' to integer: " + e.what()});
158 }
catch (
const std::exception& e) {
159 return std::unexpected(
ResultError{std::string(
"Error parsing cell value '") + value_ +
160 "' to column type: " + e.what()});
164 return parse_value<T>(allow_numeric_bools);
172 template <
typename T>
173 static constexpr bool is_optional_v =
false;
176 static std::string to_lower(std::string str) {
177 std::transform(str.begin(), str.end(), str.begin(),
178 [](
unsigned char c) { return std::tolower(c); });
183 static bool is_boolean_value(
const std::string& str) {
185 return str ==
"true" || str ==
"false" || str ==
"0" || str ==
"1";
189 template <
typename T>
190 ResultProcessingResult<T> parse_value(
bool allow_numeric_bools)
const {
192 if constexpr (std::is_same_v<T, int>) {
194 if (!is_valid_integer(value_)) {
195 return std::unexpected(ResultError{
"Cannot convert to int: invalid format"});
197 return std::stoi(value_);
198 }
else if constexpr (std::is_same_v<T, long>) {
199 if (!is_valid_integer(value_)) {
200 return std::unexpected(ResultError{
"Cannot convert to long: invalid format"});
202 return std::stol(value_);
203 }
else if constexpr (std::is_same_v<T, long long>) {
204 if (!is_valid_integer(value_)) {
205 return std::unexpected(ResultError{
"Cannot convert to long long: invalid format"});
207 return std::stoll(value_);
208 }
else if constexpr (std::is_same_v<T, unsigned long>) {
209 if (!is_valid_unsigned_integer(value_)) {
210 return std::unexpected(ResultError{
"Cannot convert to unsigned long: invalid format"});
212 return std::stoul(value_);
213 }
else if constexpr (std::is_same_v<T, unsigned long long>) {
214 if (!is_valid_unsigned_integer(value_)) {
215 return std::unexpected(
216 ResultError{
"Cannot convert to unsigned long long: invalid format"});
218 return std::stoull(value_);
219 }
else if constexpr (std::is_same_v<T, float>) {
220 if (!is_valid_float(value_)) {
221 return std::unexpected(ResultError{
"Cannot convert to float: invalid format"});
223 return std::stof(value_);
224 }
else if constexpr (std::is_same_v<T, double>) {
225 if (!is_valid_float(value_)) {
226 return std::unexpected(ResultError{
"Cannot convert to double: invalid format"});
228 return std::stod(value_);
229 }
else if constexpr (std::is_same_v<T, long double>) {
230 if (!is_valid_float(value_)) {
231 return std::unexpected(ResultError{
"Cannot convert to long double: invalid format"});
233 return std::stold(value_);
234 }
else if constexpr (std::is_same_v<T, std::string>) {
236 }
else if constexpr (is_optional_v<T>) {
237 using ValueType =
typename T::value_type;
238 auto result = as<ValueType>(allow_numeric_bools);
242 return T{std::nullopt};
245 return std::unexpected(
246 ResultError{std::string(
"Unsupported type conversion from '") + value_ +
"'"});
248 }
catch (
const std::exception& e) {
249 return std::unexpected(
250 ResultError{std::string(
"Error parsing cell value '") + value_ +
"': " + e.what()});
255 static bool is_valid_integer(
const std::string& str) {
261 if (str[0] ==
'-' || str[0] ==
'+') {
265 return str.length() > start && std::all_of(str.begin() +
static_cast<std::ptrdiff_t
>(start),
266 str.end(), [](
char c) { return std::isdigit(c); });
269 static bool is_valid_unsigned_integer(
const std::string& str) {
279 return str.length() > start && std::all_of(str.begin() +
static_cast<std::ptrdiff_t
>(start),
280 str.end(), [](
char c) { return std::isdigit(c); });
283 static bool is_valid_float(
const std::string& str) {
288 bool has_digit =
false;
289 bool has_decimal =
false;
290 bool has_exponent =
false;
293 if (str[0] ==
'-' || str[0] ==
'+') {
297 for (; i < str.length(); i++) {
298 const char c = str[i];
300 if (std::isdigit(c)) {
302 }
else if (c ==
'.') {
303 if (has_decimal || has_exponent) {
307 }
else if (c ==
'e' || c ==
'E') {
308 if (!has_digit || has_exponent) {
313 if (i + 1 < str.length() && (str[i + 1] ==
'+' || str[i + 1] ==
'-')) {
316 if (i + 1 >= str.length()) {
328 template <
typename T>
329 ResultProcessingResult<std::optional<T>> parse_value(
bool allow_numeric_bools)
const
330 requires requires { parse_value<T>(allow_numeric_bools); }
333 return std::optional<T>{std::nullopt};
336 auto result = parse_value<T>(allow_numeric_bools);
338 return std::unexpected(result.error());
340 return std::optional<T>{*result};
346constexpr bool Cell::is_optional_v<std::optional<T>> =
true;
361 if (
index >= cells_.size()) {
362 return std::unexpected(
ResultError{
"Cell index out of range"});
364 return &cells_[
index];
371 if (column_names_.empty()) {
372 return std::unexpected(
ResultError{
"Column names not available"});
375 for (
size_t i = 0; i < column_names_.size(); ++i) {
376 if (column_names_[i] == name && i < cells_.size()) {
379 if (column_names_[i] == name) {
381 return std::unexpected(
ResultError{
"Column found but missing cell data: " + name});
385 return std::unexpected(
ResultError{
"Column name not found: " + name});
392 template <
typename T>
394 return get<T>(
index,
false);
402 template <
typename T>
406 return std::unexpected(cell.error());
408 return (*cell)->template as<T>(allow_numeric_bools);
415 template <
typename T>
417 return get<T>(name,
false);
425 template <
typename T>
429 return std::unexpected(cell.error());
431 return (*cell)->template as<T>(allow_numeric_bools);
439 template <
typename T,
typename ColType>
441 return get<T>(
column,
false);
450 template <
typename T,
typename ColType>
452 if constexpr (
requires {
453 {
column.column_name } -> std::convertible_to<std::string_view>;
456 return get<T>(std::string(
column.column_name), allow_numeric_bools);
457 }
else if constexpr (
requires {
458 {
column.
name } -> std::convertible_to<std::string_view>;
461 return get<T>(std::string(
column.
name), allow_numeric_bools);
462 }
else if constexpr (std::is_same_v<ColType, std::string> ||
463 std::is_same_v<ColType, std::string_view> ||
464 std::is_convertible_v<ColType, std::string_view>) {
467 return get<T>(std::string(
column), allow_numeric_bools);
468 }
else if constexpr (std::is_convertible_v<ColType, size_t>) {
470 return get<T>(
static_cast<size_t>(
column), allow_numeric_bools);
475 {
column } -> std::convertible_to<std::string_view>;
478 {
column } -> std::convertible_to<size_t>;
481 {
column.
name } -> std::convertible_to<std::string_view>;
484 {
column.column_name } -> std::convertible_to<std::string_view>;
486 "Column must be convertible to a string, size_t, or have a name/column_name member");
489 return std::unexpected(
ResultError{
"Invalid column type"});
496 template <auto MemberPtr>
499 using ColumnType = std::remove_reference_t<decltype(std::declval<Class>().*MemberPtr)>;
500 using ValueType =
typename ColumnType::value_type;
503 static constexpr auto column_name = ColumnType::name;
506 return get<ValueType>(std::string(column_name));
512 template <auto MemberPtr>
515 using ColumnType = std::remove_reference_t<decltype(std::declval<Class>().*MemberPtr)>;
516 using ValueType =
typename ColumnType::value_type;
519 static constexpr auto column_name = ColumnType::name;
522 return get<std::optional<ValueType>>(std::string(column_name));
527 size_t size()
const {
return cells_.size(); }
531 const std::vector<std::string>&
column_names()
const {
return column_names_; }
534 std::stringstream result;
536 for (
const auto& cell : cells_) {
537 result << cell.raw_value() <<
" | ";
543 std::vector<Cell> cells_;
544 std::vector<std::string> column_names_;
548template <
typename... Types>
556 return std::get<I>(a.
values);
561template <
typename ResultSet,
typename... Types>
574 auto operator*()
const {
return get_row_values(std::make_index_sequence<
sizeof...(Types)>{}); }
577 template <
size_t... Indices>
578 RowAdapter<Types...> get_row_values(std::index_sequence<Indices...>)
const {
582 std::tuple<Types...>{[&row,
this]() -> std::tuple_element_t<Indices, std::tuple<Types...>> {
583 using ResultType = std::tuple_element_t<Indices, std::tuple<Types...>>;
585 bool allow_numeric_bools = std::is_same_v<ResultType, bool>;
586 auto result = row.template get<ResultType>(
column_indices[Indices], allow_numeric_bools);
597template <
typename ResultSet,
typename... Types>
619 size_t size()
const {
return rows_.size(); }
623 bool empty()
const {
return rows_.empty(); }
643 if (
index >= column_names_.size()) {
644 return std::unexpected(
ResultError{
"Column index out of range"});
646 return column_names_.at(
index);
651 const std::vector<std::string>&
column_names()
const {
return column_names_; }
655 auto begin()
const {
return rows_.begin(); }
659 auto end()
const {
return rows_.end(); }
665 template <
typename T>
667 std::vector<T> result;
668 result.reserve(rows_.size());
670 for (
const auto& row : rows_) {
671 auto transformed = mapper(row);
673 result.push_back(std::move(*transformed));
683 template <
typename... Types>
686 std::array<size_t,
sizeof...(Types)> indices;
687 for (
size_t i = 0; i <
sizeof...(Types); ++i) {
690 return as<Types...>(indices);
697 template <
typename... Types>
698 auto as(
const std::array<
size_t,
sizeof...(Types)>& indices)
const {
706 template <
typename... Types>
708 std::array<size_t,
sizeof...(Types)> indices;
710 for (
size_t i = 0; i <
sizeof...(Types); ++i) {
712 for (
size_t col_idx = 0; col_idx < column_names_.size(); ++col_idx) {
714 indices[i] = col_idx;
721 indices[i] = (i < column_names_.size()) ? i : 0;
725 return as<Types...>(indices);
748 template <
typename Table,
typename P1,
typename... Ps>
751 constexpr size_t num_cols = 1 +
sizeof...(Ps);
756 if constexpr (
sizeof...(Ps) > 0) {
758 ((
column_names[i++] = get_column_name_from_ptr<Table>(mps)), ...);
786 template <
typename Table,
typename P1,
typename... Ps>
789 return with_schema<std::remove_reference_t<Table>>(mp1, mps...);
794 for (
const auto& row : rows_) {
795 result += row.to_string();
802 std::vector<Row> rows_;
803 std::vector<std::string> column_names_;
811template <query::SqlExpr Query>
815 std::vector<std::string> lines;
820 while ((next_pos = raw_results.find(
'\n', pos)) != std::string::npos) {
821 lines.push_back(raw_results.substr(pos, next_pos - pos));
826 if (pos < raw_results.size()) {
827 lines.push_back(raw_results.substr(pos));
835 std::vector<std::string> column_names;
836 const std::string header = lines[0];
839 while ((next_pos = header.find(
'|', pos)) != std::string::npos) {
840 column_names.push_back(header.substr(pos, next_pos - pos));
845 if (pos < header.size()) {
846 column_names.push_back(header.substr(pos));
850 std::vector<Row> rows;
852 for (
size_t i = 1; i < lines.size(); ++i) {
853 const auto& line = lines[i];
861 std::vector<Cell> cells;
864 while ((next_pos = line.find(
'|', pos)) != std::string::npos) {
865 cells.emplace_back(line.substr(pos, next_pos - pos));
870 if (pos < line.size()) {
871 cells.emplace_back(line.substr(pos));
875 rows.emplace_back(std::move(cells), column_names);
878 return ResultSet{std::move(rows), std::move(column_names)};
879 }
catch (
const std::exception& e) {
880 return std::unexpected(
ResultError{std::string(
"Error parsing results: ") + e.what()});
888template <
typename... Types>
889struct tuple_size<
relx::result::RowAdapter<Types...>>
890 : std::integral_constant<size_t, sizeof...(Types)> {};
892template <
size_t I,
typename... Types>
893struct tuple_element<I,
relx::result::RowAdapter<Types...>> {
894 using type = std::tuple_element_t<I, std::tuple<Types...>>;
Represents a single cell value from a database result.
bool is_null() const
Check if the cell contains a NULL value.
ResultProcessingResult< T > as() const
Parse the cell's value as the specified type.
ResultProcessingResult< T > as(bool allow_numeric_bools) const
Parse the cell's value as the specified type with an option to allow numeric boolean conversion.
Cell(std::string value)
Constructs a cell with a raw string value from the database.
const std::string & raw_value() const
Get the raw string value.
Represents the result set from a database query.
auto begin() const
Get an iterator to the beginning of the rows.
auto with_schema(const Table &, P1 mp1, Ps... mps) const
Creates a structured binding view using table schema information.
const Row & at(size_t index) const
Get a row by index.
auto as(const std::array< size_t, sizeof...(Types)> &indices) const
Helper for structured binding with explicit types and column indices.
std::string to_string() const
auto as(const std::array< std::string, sizeof...(Types)> &column_names) const
Helper for structured binding with explicit types and column names.
bool empty() const
Check if the result set is empty.
const Row & operator[](size_t index) const
Access a row by index using the subscript operator.
size_t size() const
Get the number of rows in the result set.
auto as() const
Helper for structured binding with explicit types.
ResultSet(std::vector< Row > rows, std::vector< std::string > column_names={})
Constructs a result set with rows and column names.
ResultProcessingResult< std::string > column_name(size_t index) const
Get the name of a column by index.
ResultSet()=default
Default constructor.
const std::vector< std::string > & column_names() const
Get the column names.
size_t column_count() const
Get the number of columns in the result set.
auto with_schema(P1 mp1, Ps... mps) const
Creates a structured binding view using table schema information.
auto end() const
Get an iterator to the end of the rows.
std::vector< T > transform(std::function< ResultProcessingResult< T >(const Row &)> mapper) const
Transform each row into an object of type T.
Represents a single row from a database result.
ResultProcessingResult< const Cell * > get_cell(const std::string &name) const
Get a cell by column name.
ResultProcessingResult< T > get(size_t index) const
Get a typed value by index.
ResultProcessingResult< T > get(const ColType &column, bool allow_numeric_bools) const
Get a typed value using a schema column object with allow_numeric_bools.
auto get_optional() const
Get an optional typed value using a member pointer.
const std::vector< std::string > & column_names() const
Get the column names.
ResultProcessingResult< T > get(size_t index, bool allow_numeric_bools) const
Get a typed value by index with allow_numeric_bools.
ResultProcessingResult< T > get(const ColType &column) const
Get a typed value using a schema column object.
Row(std::vector< Cell > cells, std::vector< std::string > column_names={})
Constructs a row with cells and column names.
ResultProcessingResult< T > get(const std::string &name) const
Get a typed value by column name.
std::string to_string() const
auto get() const
Get a typed value using a member pointer.
ResultProcessingResult< const Cell * > get_cell(size_t index) const
Get a cell by index.
ResultProcessingResult< T > get(const std::string &name, bool allow_numeric_bools) const
Get a typed value by column name with allow_numeric_bools.
size_t size() const
Get the number of cells in this row.
Represents a column in a database table.
static constexpr auto name
The column name.
Represents an index on a table.
Concept for a database table type.
typename class_of_t< T >::type class_of_t_t
static constexpr auto class_of(T C::*)
Helper to get the class type from a member pointer.
std::string get_column_name_from_ptr(ColumnMemberPtr ptr)
Get column name from a member pointer.
typename column_member_value< Table, ColumnMemberPtr >::type column_member_value_t
ResultProcessingResult< ResultSet > parse(const Query &, const std::string &raw_results)
Parse raw results from a database into a typed ResultSet (eager parsing)
std::expected< T, ResultError > ResultProcessingResult
Type alias for result of processing operations.
constexpr std::string_view get_column_name()
Gets the column name from a column member pointer.
Error type for result processing operations.
Class to support structured binding for ResultSet.
std::tuple< Types... > values
friend const auto & get(const RowAdapter &a)
Iterator that yields RowAdapters.
const ResultSet & results
std::array< size_t, sizeof...(Types)> column_indices
RowIterator & operator++()
bool operator!=(const RowIterator &other) const
View class for structured binding support.
RowIterator< ResultSet, Types... > begin() const
RowIterator< ResultSet, Types... > end() const
std::array< size_t, sizeof...(Types)> column_indices
const ResultSet & results
Helper to get the value type from a column member pointer.
typename column_type::value_type type
std::remove_reference_t< decltype(std::declval< Table >().*std::declval< ColumnMemberPtr >())> column_type
static T from_sql_string(const std::string &value)
Parse a SQL string representation to a C++ value.