diff --git a/Sources/StructuredQueriesCore/Statements/Select.swift b/Sources/StructuredQueriesCore/Statements/Select.swift index b3396422..d1704888 100644 --- a/Sources/StructuredQueriesCore/Statements/Select.swift +++ b/Sources/StructuredQueriesCore/Statements/Select.swift @@ -710,6 +710,36 @@ extension Select { ) } + @_disfavoredOverload + @_documentation(visibility: private) + public func join( + // TODO: Report issue to Swift team. Using 'some' crashes the compiler. + _ other: any SelectStatementOf, + on constraint: ( + (From.TableColumns, Joins.TableColumns, F.TableColumns) + ) -> some QueryExpression + ) -> Select<(), From, (Joins, F)> where Joins: Table { + let other = other.asSelect() + let join = _JoinClause( + operator: .inner, + table: F.self, + constraint: constraint( + (From.columns, Joins.columns, F.columns) + ) + ) + return Select<(), From, (Joins, F)>( + isEmpty: isEmpty || other.isEmpty, + distinct: distinct || other.distinct, + columns: columns + other.columns, + joins: joins + [join] + other.joins, + where: `where` + other.where, + group: group + other.group, + having: having + other.having, + order: order + other.order, + limit: other.limit ?? limit + ) + } + /// Creates a new select statement from this one by left-joining another table. /// /// - Parameters: @@ -849,6 +879,37 @@ extension Select { ) } + @_disfavoredOverload + @_documentation(visibility: private) + public func leftJoin( + // TODO: Report issue to Swift team. Using 'some' crashes the compiler. + _ other: any SelectStatementOf, + on constraint: ( + (From.TableColumns, Joins.TableColumns, F.TableColumns) + ) -> some QueryExpression + ) -> Select<(), From, (Joins, F._Optionalized)> + where Joins: Table { + let other = other.asSelect() + let join = _JoinClause( + operator: .left, + table: F.self, + constraint: constraint( + (From.columns, Joins.columns, F.columns) + ) + ) + return Select<(), From, (Joins, F._Optionalized)>( + isEmpty: isEmpty || other.isEmpty, + distinct: distinct || other.distinct, + columns: columns + other.columns, + joins: joins + [join] + other.joins, + where: `where` + other.where, + group: group + other.group, + having: having + other.having, + order: order + other.order, + limit: other.limit ?? limit + ) + } + /// Creates a new select statement from this one by right-joining another table. /// /// - Parameters: @@ -988,6 +1049,37 @@ extension Select { ) } + @_disfavoredOverload + @_documentation(visibility: private) + public func rightJoin( + // TODO: Report issue to Swift team. Using 'some' crashes the compiler. + _ other: any SelectStatementOf, + on constraint: ( + (From.TableColumns, Joins.TableColumns, F.TableColumns) + ) -> some QueryExpression + ) -> Select<(), From._Optionalized, (Joins._Optionalized, F)> + where Joins: Table { + let other = other.asSelect() + let join = _JoinClause( + operator: .right, + table: F.self, + constraint: constraint( + (From.columns, Joins.columns, F.columns) + ) + ) + return Select<(), From._Optionalized, (Joins._Optionalized, F)>( + isEmpty: isEmpty || other.isEmpty, + distinct: distinct || other.distinct, + columns: columns + other.columns, + joins: joins + [join] + other.joins, + where: `where` + other.where, + group: group + other.group, + having: having + other.having, + order: order + other.order, + limit: other.limit ?? limit + ) + } + /// Creates a new select statement from this one by full-joining another table. /// /// - Parameters: @@ -1127,6 +1219,37 @@ extension Select { ) } + @_disfavoredOverload + @_documentation(visibility: private) + public func fullJoin( + // TODO: Report issue to Swift team. Using 'some' crashes the compiler. + _ other: any SelectStatementOf, + on constraint: ( + (From.TableColumns, Joins.TableColumns, F.TableColumns) + ) -> some QueryExpression + ) -> Select<(), From._Optionalized, (Joins._Optionalized, F._Optionalized)> + where Joins: Table { + let other = other.asSelect() + let join = _JoinClause( + operator: .full, + table: F.self, + constraint: constraint( + (From.columns, Joins.columns, F.columns) + ) + ) + return Select<(), From._Optionalized, (Joins._Optionalized, F._Optionalized)>( + isEmpty: isEmpty || other.isEmpty, + distinct: distinct || other.distinct, + columns: columns + other.columns, + joins: joins + [join] + other.joins, + where: `where` + other.where, + group: group + other.group, + having: having + other.having, + order: order + other.order, + limit: other.limit ?? limit + ) + } + /// Creates a new select statement from this one by appending a predicate to its `WHERE` clause. /// /// - Parameter keyPath: A key path from this select's table to a Boolean expression to filter by. diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index 3039785e..a4a4c817 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1406,6 +1406,22 @@ extension SnapshotTests { @Test func singleJoinChaining() { let base = Reminder.group(by: \.id).join(ReminderTag.all) { $0.id.eq($1.reminderID) } _ = base.select { r, _ in r.isCompleted } + _ = base.join(RemindersList.all) { _, _, _ in true } + _ = base.leftJoin(RemindersList.all) { _, _, _ in true } + _ = base.rightJoin(RemindersList.all) { _, _, _ in true } + _ = base.fullJoin(RemindersList.all) { _, _, _ in true } + _ = base + .join(RemindersList.all) { _, _, _ in true } + .join(RemindersList.all) { _, _, _, _ in true } + _ = base + .leftJoin(RemindersList.all) { _, _, _ in true } + .leftJoin(RemindersList.all) { _, _, _, _ in true } + _ = base + .rightJoin(RemindersList.all) { _, _, _ in true } + .rightJoin(RemindersList.all) { _, _, _, _ in true } + _ = base + .fullJoin(RemindersList.all) { _, _, _ in true } + .fullJoin(RemindersList.all) { _, _, _, _ in true } _ = base.where { r, _ in r.isCompleted } _ = base.group { r, _ in r.isCompleted } _ = base.having { r, _ in r.isCompleted }