Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/duckdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ query(conn).value();
- Resource management through `Ref<T>`
- Auto-incrementing primary keys
- Various data types including VARCHAR, TIMESTAMP, DATE
- Complex queries with WHERE clauses, ORDER BY, LIMIT, JOINs
- Complex queries with WHERE clauses, ORDER BY, LIMIT, OFFSET, JOINs
- LIKE and pattern matching operations
- Mathematical operations and string functions
- JSON data types
Expand Down
3 changes: 1 addition & 2 deletions docs/group_by_and_aggregations.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,9 @@ In this example, each field in the `Result` struct must have a name that matches

## Notes

- The `group_by` clause must be used before `order_by` or `limit` clauses
- The `group_by` clause must be used before `order_by` or `limit` and `offset` clauses
- You cannot use `group_by` multiple times in the same query
- Aggregation functions can be used with or without `group_by`
- The result type must match the structure of your select statement. Note that fields are matched by name, not order.
- All aggregations are type-safe and will map to appropriate SQL types
- The `Result<...>` type provides error handling; use `.value()` to extract the result (will throw an exception if there's an error) or handle errors as needed

6 changes: 4 additions & 2 deletions docs/literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>>
| where("age"_c >= 18 and "last_name"_c == "Simpson")
| order_by("first_name"_c.desc())
| limit(10);
| limit(10)
| offset(3);
```

This generates:
Expand All @@ -123,5 +124,6 @@ SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE ("age" >= 18) AND ("last_name" = 'Simpson')
ORDER BY "first_name" DESC
LIMIT 10;
LIMIT 10
OFFSET 3;
```
3 changes: 1 addition & 2 deletions docs/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ const auto result = session(pool)
- Connection pooling for high-performance applications
- Auto-incrementing primary keys
- Various data types including VARCHAR, TIMESTAMP, DATE
- Complex queries with WHERE clauses, ORDER BY, LIMIT, JOINs
- Complex queries with WHERE clauses, ORDER BY, LIMIT, OFFSET, JOINs
- LIKE and pattern matching operations
- Mathematical operations and string functions

26 changes: 26 additions & 0 deletions docs/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,32 @@ ORDER BY "age"
LIMIT 2;
```

You can also combine `limit` with `offset` to perform paging:

```cpp
using namespace sqlgen;
using namespace sqlgen::literals;

const auto query = sqlgen::read<std::vector<Person>> |
order_by("age"_c) |
limit(2) |
offset(3);

const auto skip_three = query(conn).value();
```

This generates the following SQL:

```sql
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
ORDER BY "age"
LIMIT 2
OFFSET 3;
```

- **SQLite and MySql Limitation*: You cannot use `offset` without `limit`.

### With ranges

Read results as a lazy range:
Expand Down
3 changes: 2 additions & 1 deletion docs/select_from.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ const auto query = select_from<Person>(
"first_name"_c,
"last_name"_c,
"age"_c
)
)
| where("age"_c >= 18) // Filter results
| order_by("last_name"_c, "first_name"_c) // Order results
| limit(10) // Limit number of results
| offset(5) // Offset the result set
| to<std::vector<Person>>; // Convert to container
```

Expand Down
1 change: 1 addition & 0 deletions include/sqlgen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "sqlgen/is_connection.hpp"
#include "sqlgen/joins.hpp"
#include "sqlgen/limit.hpp"
#include "sqlgen/offset.hpp"
#include "sqlgen/literals.hpp"
#include "sqlgen/operations.hpp"
#include "sqlgen/order_by.hpp"
Expand Down
17 changes: 8 additions & 9 deletions include/sqlgen/create_as.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ namespace sqlgen {

template <class ValueType, class TableOrQueryType, class AliasType,
class FieldsType, class JoinsType, class WhereType, class GroupByType,
class OrderByType, class LimitType, class ToType, class Connection>
class OrderByType, class LimitType, class OffsetType, class ToType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_as_impl(
const Ref<Connection>& _conn, const dynamic::CreateAs::What _what,
const SelectFrom<TableOrQueryType, AliasType, FieldsType, JoinsType,
WhereType, GroupByType, OrderByType, LimitType, ToType>&
WhereType, GroupByType, OrderByType, LimitType, OffsetType, ToType>&
_as,
const bool _or_replace, const bool _if_not_exists) {
using TableTupleType =
transpilation::table_tuple_t<TableOrQueryType, AliasType, JoinsType>;

const auto query = transpilation::to_create_as<
ValueType, TableTupleType, AliasType, FieldsType, TableOrQueryType,
JoinsType, WhereType, GroupByType, OrderByType, LimitType>(
JoinsType, WhereType, GroupByType, OrderByType, LimitType, OffsetType>(
_what, _or_replace, _if_not_exists, _as.fields_, _as.from_, _as.joins_,
_as.where_, _as.limit_);
_as.where_, _as.limit_, _as.offset_);

return _conn->execute(_conn->to_sql(query)).transform([&](const auto&) {
return _conn;
Expand All @@ -42,12 +42,12 @@ Result<Ref<Connection>> create_as_impl(

template <class ValueType, class TableOrQueryType, class AliasType,
class FieldsType, class JoinsType, class WhereType, class GroupByType,
class OrderByType, class LimitType, class ToType, class Connection>
class OrderByType, class LimitType, class OffsetType, class ToType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_as_impl(
const Result<Ref<Connection>>& _res, const dynamic::CreateAs::What _what,
const SelectFrom<TableOrQueryType, AliasType, FieldsType, JoinsType,
WhereType, GroupByType, OrderByType, LimitType, ToType>&
WhereType, GroupByType, OrderByType, LimitType, OffsetType, ToType>&
_as,
const bool _or_replace, const bool _if_not_exists) {
return _res.and_then([&](const auto& _conn) {
Expand All @@ -58,10 +58,10 @@ Result<Ref<Connection>> create_as_impl(

template <class ValueType, class TableOrQueryType, class AliasType,
class FieldsType, class JoinsType, class WhereType, class GroupByType,
class OrderByType, class LimitType, class ToType>
class OrderByType, class LimitType, class OffsetType, class ToType>
struct CreateAs {
using As = SelectFrom<TableOrQueryType, AliasType, FieldsType, JoinsType,
WhereType, GroupByType, OrderByType, LimitType, ToType>;
WhereType, GroupByType, OrderByType, LimitType, OffsetType, ToType>;

static_assert(
requires(transpilation::extract_table_t<As, false> a) {
Expand Down Expand Up @@ -108,4 +108,3 @@ inline auto create_or_replace_view_as(const SelectFrom<Args...>& _as) {
}; // namespace sqlgen

#endif

2 changes: 1 addition & 1 deletion include/sqlgen/duckdb/Connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class SQLGEN_API Connection {
}));

const auto select_from = dynamic::SelectFrom{
.table_or_query = _table, .fields = fields, .limit = dynamic::Limit{0}};
.table_or_query = _table, .fields = fields, .limit = dynamic::Limit{0}, .offset = dynamic::Offset{0}};

return DuckDBResult::make(to_sql(select_from), conn_)
.transform([&](const auto &_res) {
Expand Down
14 changes: 14 additions & 0 deletions include/sqlgen/dynamic/Offset.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef SQLGEN_DYNAMIC_OFFSET_HPP_
#define SQLGEN_DYNAMIC_OFFSET_HPP_

#include <cstddef>

namespace sqlgen::dynamic {

struct Offset {
size_t val;
};

} // namespace sqlgen::dynamic

#endif
2 changes: 2 additions & 0 deletions include/sqlgen/dynamic/SelectFrom.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "GroupBy.hpp"
#include "JoinType.hpp"
#include "Limit.hpp"
#include "Offset.hpp"
#include "Operation.hpp"
#include "OrderBy.hpp"
#include "Table.hpp"
Expand Down Expand Up @@ -46,6 +47,7 @@ struct SelectFrom {
std::optional<GroupBy> group_by = std::nullopt;
std::optional<OrderBy> order_by = std::nullopt;
std::optional<Limit> limit = std::nullopt;
std::optional<Offset> offset = std::nullopt;
};

} // namespace sqlgen::dynamic
Expand Down
14 changes: 14 additions & 0 deletions include/sqlgen/offset.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef SQLGEN_OFFSET_HPP_
#define SQLGEN_OFFSET_HPP_

#include "transpilation/Offset.hpp"

namespace sqlgen {

using Offset = transpilation::Offset;

inline auto offset(const size_t _val) { return Offset{_val}; };

} // namespace sqlgen

#endif
48 changes: 32 additions & 16 deletions include/sqlgen/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "internal/is_range.hpp"
#include "is_connection.hpp"
#include "limit.hpp"
#include "offset.hpp"
#include "order_by.hpp"
#include "transpilation/order_by_t.hpp"
#include "transpilation/read_to_select_from.hpp"
Expand All @@ -18,40 +19,40 @@
namespace sqlgen {

template <class ContainerType, class WhereType, class OrderByType,
class LimitType, class Connection>
class LimitType, class OffsetType, class Connection>
requires is_connection<Connection>
auto read_impl(const Ref<Connection>& _conn, const WhereType& _where,
const LimitType& _limit) {
const LimitType& _limit, const OffsetType& _offset) {
using ValueType = transpilation::value_t<ContainerType>;
const auto query =
transpilation::read_to_select_from<ValueType, WhereType, OrderByType,
LimitType>(_where, _limit);
LimitType, OffsetType>(_where, _limit, _offset);
return _conn->template read<ContainerType>(query);
}

template <class ContainerType, class WhereType, class OrderByType,
class LimitType, class Connection>
class LimitType, class OffsetType, class Connection>
requires is_connection<Connection>
auto read_impl(const Result<Ref<Connection>>& _res, const WhereType& _where,
const LimitType& _limit) {
const LimitType& _limit, const OffsetType& _offset) {
return _res.and_then([&](const auto& _conn) {
return read_impl<ContainerType, WhereType, OrderByType, LimitType>(
_conn, _where, _limit);
return read_impl<ContainerType, WhereType, OrderByType, LimitType, OffsetType>(
_conn, _where, _limit, _offset);
});
}

template <class Type, class WhereType = Nothing, class OrderByType = Nothing,
class LimitType = Nothing>
class LimitType = Nothing, class OffsetType = Nothing>
struct Read {
auto operator()(const auto& _conn) const {
if constexpr (std::ranges::input_range<std::remove_cvref_t<Type>> ||
internal::is_range_v<Type>) {
return read_impl<Type, WhereType, OrderByType, LimitType>(_conn, where_,
limit_);
return read_impl<Type, WhereType, OrderByType, LimitType, OffsetType>(_conn, where_,
limit_, offset_);

} else {
return read_impl<std::vector<Type>, WhereType, OrderByType, LimitType>(
_conn, where_, limit_)
return read_impl<std::vector<Type>, WhereType, OrderByType, LimitType, OffsetType>(
_conn, where_, limit_, offset_)
.and_then([](auto&& _vec) -> Result<Type> {
if (_vec.size() != 1) {
return error(
Expand All @@ -73,7 +74,9 @@ struct Read {
"You cannot call order_by(...) before where(...).");
static_assert(std::is_same_v<LimitType, Nothing>,
"You cannot call limit(...) before where(...).");
return Read<Type, ConditionType, OrderByType, LimitType>{
static_assert(std::is_same_v<OffsetType, Nothing>,
"You cannot call offset(...) before where(...).");
return Read<Type, ConditionType, OrderByType, LimitType, OffsetType>{
.where_ = _where.condition};
}

Expand All @@ -85,25 +88,38 @@ struct Read {
"than one column).");
static_assert(std::is_same_v<LimitType, Nothing>,
"You cannot call limit(...) before order_by.");
static_assert(std::is_same_v<OffsetType, Nothing>,
"You cannot call offset(...) before order_by.");
static_assert(sizeof...(ColTypes) != 0,
"You must assign at least one column to order by(...).");
return Read<Type, WhereType,
transpilation::order_by_t<
transpilation::value_t<Type>, Nothing,
typename std::remove_cvref_t<ColTypes>::ColType...>,
LimitType>{.where_ = _r.where_};
LimitType, OffsetType>{.where_ = _r.where_};
}

friend auto operator|(const Read& _r, const Limit& _limit) {
static_assert(std::is_same_v<LimitType, Nothing>,
"You cannot call limit(...) twice.");
return Read<Type, WhereType, OrderByType, Limit>{.where_ = _r.where_,
.limit_ = _limit};
return Read<Type, WhereType, OrderByType, Limit, OffsetType>{.where_ = _r.where_,
.limit_ = _limit,
.offset_ = _r.offset_};
}

friend auto operator|(const Read& _r, const Offset& _offset) {
static_assert(std::is_same_v<OffsetType, Nothing>,
"You cannot call offset(...) twice.");
return Read<Type, WhereType, OrderByType, LimitType, Offset>{.where_ = _r.where_,
.limit_ = _r.limit_,
.offset_ = _offset};
}

WhereType where_;

LimitType limit_;

OffsetType offset_;
};

template <class ContainerType>
Expand Down
Loading
Loading