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
162 changes: 161 additions & 1 deletion Sources/StructuredQueriesCore/Statements/Select.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,38 @@ extension Select {
return select
}

/// Creates a new select statement from this one by appending a predicate to its `WHERE` clause.
///
/// - Parameter predicate: A closure that produces a Boolean query expression from this select's
/// tables.
/// - Returns: A new select statement that appends the given predicate to its `WHERE` clause.
@_disfavoredOverload
public func `where`(
_ predicate: (From.TableColumns, Joins.TableColumns) -> some QueryExpression<
some _OptionalPromotable<Bool?>
>
) -> Self
where Joins: Table {
var select = self
select.where.append(predicate(From.columns, Joins.columns).queryFragment)
return select
}

/// Creates a new select statement from this one by appending a predicate to its `WHERE` clause.
///
/// - Parameter predicate: A result builder closure that returns a Boolean expression to filter
/// by.
/// - Returns: A new select statement that appends the given predicate to its `WHERE` clause.
public func `where`(
@QueryFragmentBuilder<Bool>
_ predicate: (From.TableColumns, Joins.TableColumns) -> [QueryFragment]
) -> Self
where Joins: Table {
var select = self
select.where.append(contentsOf: predicate(From.columns, Joins.columns))
return select
}

public func and(_ other: Where<From>) -> Self {
var select = self
select.where = (select.where + other.predicates).removingDuplicates()
Expand Down Expand Up @@ -1221,6 +1253,32 @@ extension Select {
_group(by: grouping)
}

/// Creates a new select statement from this one by appending the given column to its `GROUP BY`
/// clause.
///
/// - Parameter grouping: A closure that returns a column to group by from this select's tables.
/// - Returns: A new select statement that groups by the given column.
public func group<C: QueryExpression>(
by grouping: (From.TableColumns, Joins.TableColumns) -> C
) -> Self where Joins: Table {
_group(by: grouping)
}

/// Creates a new select statement from this one by appending the given columns to its `GROUP BY`
/// clause.
///
/// - Parameter grouping: A closure that returns a column to group by from this select's tables.
/// - Returns: A new select statement that groups by the given column.
public func group<
C1: QueryExpression,
C2: QueryExpression,
each C3: QueryExpression
>(
by grouping: (From.TableColumns, Joins.TableColumns) -> (C1, C2, repeat each C3)
) -> Self where Joins: Table {
_group(by: grouping)
}

private func _group<
each C: QueryExpression,
each J: Table
Expand All @@ -1235,6 +1293,17 @@ extension Select {
return select
}

private func _group<each C: QueryExpression>(
by grouping: (From.TableColumns, Joins.TableColumns) -> (repeat each C)
) -> Self where Joins: Table {
var select = self
select.group
.append(
contentsOf: Array(repeat each grouping(From.columns, Joins.columns))
)
return select
}

/// Creates a new select statement from this one by appending a predicate to its `HAVING` clause.
///
/// - Parameter predicate: A closure that produces a Boolean query expression from this select's
Expand Down Expand Up @@ -1267,6 +1336,38 @@ extension Select {
return select
}

/// Creates a new select statement from this one by appending a predicate to its `HAVING` clause.
///
/// - Parameter predicate: A closure that produces a Boolean query expression from this select's
/// tables.
/// - Returns: A new select statement that appends the given predicate to its `HAVING` clause.
@_disfavoredOverload
public func having(
_ predicate: (From.TableColumns, Joins.TableColumns) -> some QueryExpression<
some _OptionalPromotable<Bool?>
>
) -> Self
where Joins: Table {
var select = self
select.having.append(predicate(From.columns, Joins.columns).queryFragment)
return select
}

/// Creates a new select statement from this one by appending a predicate to its `HAVING` clause.
///
/// - Parameter predicate: A result builder closure that returns a Boolean expression to filter
/// by.
/// - Returns: A new select statement that appends the given predicate to its `HAVING` clause.
public func having(
@QueryFragmentBuilder<Bool>
_ predicate: (From.TableColumns, Joins.TableColumns) -> [QueryFragment]
) -> Self
where Joins: Table {
var select = self
select.having.append(contentsOf: predicate(From.columns, Joins.columns))
return select
}

/// Creates a new select statement from this one by appending a column to its `ORDER BY` clause.
///
/// - Parameter ordering: A key path to a column to order by.
Expand All @@ -1291,6 +1392,20 @@ extension Select {
return select
}

/// Creates a new select statement from this one by appending columns to its `ORDER BY` clause.
///
/// - Parameter ordering: A result builder closure that returns columns to order by.
/// - Returns: A new select statement that appends the returned columns to its `ORDER BY` clause.
public func order(
@QueryFragmentBuilder<()>
by ordering: (From.TableColumns, Joins.TableColumns) -> [QueryFragment]
) -> Self
where Joins: Table {
var select = self
select.order.append(contentsOf: ordering(From.columns, Joins.columns))
return select
}

/// Creates a new select statement from this one by overriding its `LIMIT` and `OFFSET` clauses.
///
/// - Parameters:
Expand All @@ -1299,7 +1414,7 @@ extension Select {
/// - Returns: A new select statement that overrides this one's `LIMIT` and `OFFSET` clauses.
public func limit<each J: Table>(
_ maxLength: (From.TableColumns, repeat (each J).TableColumns) -> some QueryExpression<Int>,
offset: ((From.TableColumns, repeat (each J).TableColumns) -> some QueryExpression<Int>)? = nil
offset: ((From.TableColumns, repeat (each J).TableColumns) -> any QueryExpression<Int>)? = nil
) -> Self
where Joins == (repeat each J) {
var select = self
Expand All @@ -1310,6 +1425,25 @@ extension Select {
return select
}

/// Creates a new select statement from this one by overriding its `LIMIT` and `OFFSET` clauses.
///
/// - Parameters:
/// - maxLength: A closure that produces a `LIMIT` expression from this select's tables.
/// - offset: A closure that produces an `OFFSET` expression from this select's tables.
/// - Returns: A new select statement that overrides this one's `LIMIT` and `OFFSET` clauses.
public func limit(
_ maxLength: (From.TableColumns, Joins.TableColumns) -> some QueryExpression<Int>,
offset: ((From.TableColumns, Joins.TableColumns) -> any QueryExpression<Int>)? = nil
) -> Self
where Joins: Table {
var select = self
select.limit = _LimitClause(
maxLength: maxLength(From.columns, Joins.columns).queryFragment,
offset: offset?(From.columns, Joins.columns).queryFragment ?? select.limit?.offset
)
return select
}

/// Creates a new select statement from this one by overriding its `LIMIT` and `OFFSET` clauses.
///
/// - Parameters:
Expand Down Expand Up @@ -1352,6 +1486,32 @@ extension Select {
return select { _ in .count(filter: filter) }
}

/// Creates a new select statement from this one by appending `count(*)` to its selection.
///
/// - Parameter filter: A `FILTER` clause to apply to the aggregation.
/// - Returns: A new select statement that selects `count(*)`.
public func count(
filter: ((From.TableColumns, Joins.TableColumns) -> any QueryExpression<Bool>)? = nil
) -> Select<Int, From, Joins>
where Columns == (), Joins: Table {
let filter = filter?(From.columns, Joins.columns)
return select { _, _ in .count(filter: filter) }
}

/// Creates a new select statement from this one by appending `count(*)` to its selection.
///
/// - Parameter filter: A `FILTER` clause to apply to the aggregation.
/// - Returns: A new select statement that selects `count(*)`.
public func count<each C: QueryRepresentable>(
filter: ((From.TableColumns, Joins.TableColumns) -> any QueryExpression<Bool>)? = nil
) -> Select<
(repeat each C, Int), From, Joins
>
where Columns == (repeat each C), Joins: Table {
let filter = filter?(From.columns, Joins.columns)
return select { _, _ in .count(filter: filter) }
}

/// Creates a new select statement from this one by transforming its selected columns to a new
/// selection.
///
Expand Down
14 changes: 14 additions & 0 deletions Tests/StructuredQueriesTests/SelectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,20 @@ extension SnapshotTests {
}
}
}

@Test func singleJoinChaining() {
let base = Reminder.group(by: \.id).join(ReminderTag.all) { $0.id.eq($1.reminderID) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't test on the actual bug.

it is always working, without patch also working:
Reminder.group(by: \.id).join(ReminderTag.all) { $0.id.eq($1.reminderID) }

tricky is only not working at this case:
Reminder.group(by: \.id).withReminderTagJoin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this line compiles fine, the lines below it triggered what I think were equivalent compiler errors without the overloads defined in this PR. The issue doesn't seem to be related to the static dynamic member lookup.

_ = base.select { r, _ in r.isCompleted }
_ = base.where { r, _ in r.isCompleted }
_ = base.group { r, _ in r.isCompleted }
_ = base.having { r, _ in r.isCompleted }
_ = base.order { r, _ in r.isCompleted }
_ = base.limit { r, _ in r.title.length() }
_ = base.limit(1)
_ = base.count()
_ = base.count { r, _ in r.isCompleted }
_ = base.map {}
}
}
}

Expand Down