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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Builds ``SQLDelete`` queries.
public final class SQLDeleteBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLReturningBuilder {
public final class SQLDeleteBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLReturningBuilder, SQLCommonTableExpressionBuilder {
/// ``SQLDelete`` query being built.
public var delete: SQLDelete

Expand All @@ -26,6 +26,13 @@ public final class SQLDeleteBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLRe
set { self.delete.returning = newValue }
}

// See `SQLCommonTableExpressionBuilder.tableExpressionGroup`.
@inlinable
public var tableExpressionGroup: SQLCommonTableExpressionGroup? {
get { self.delete.tableExpressionGroup }
set { self.delete.tableExpressionGroup = newValue }
}

/// Create a new ``SQLDeleteBuilder``.
@inlinable
public init(_ delete: SQLDelete, on database: any SQLDatabase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// > ``SQLInsertBuilder``'s otherwise-identical public APIs overwrite the effects of any previous invocation. It
/// > would ideally be preferable to change ``SQLInsertBuilder``'s semantics in this regard, but this would be a
/// > significant breaking change in the API's behavior, and must therefore wait for a major version bump.
public final class SQLInsertBuilder: SQLQueryBuilder, SQLReturningBuilder/*, SQLUnqualifiedColumnListBuilder*/ {
public final class SQLInsertBuilder: SQLQueryBuilder, SQLReturningBuilder/*, SQLUnqualifiedColumnListBuilder*/, SQLCommonTableExpressionBuilder {
/// The ``SQLInsert`` query this builder builds.
public var insert: SQLInsert

Expand All @@ -25,6 +25,13 @@ public final class SQLInsertBuilder: SQLQueryBuilder, SQLReturningBuilder/*, SQL
set { self.insert.returning = newValue }
}

// See `SQLCommonTableExpressionBuilder.tableExpressionGroup`.
@inlinable
public var tableExpressionGroup: SQLCommonTableExpressionGroup? {
get { self.insert.tableExpressionGroup }
set { self.insert.tableExpressionGroup = newValue }
}

/// Creates a new `SQLInsertBuilder`.
@inlinable
public init(_ insert: SQLInsert, on database: any SQLDatabase) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Builds top-level ``SQLUnion`` queries which may be executed on their own.
public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher, SQLCommonUnionBuilder {
public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher, SQLCommonUnionBuilder, SQLCommonTableExpressionBuilder {
// See `SQLCommonUnionBuilder.union`.
public var union: SQLUnion

Expand All @@ -12,6 +12,13 @@ public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher, SQLCommonU
self.union
}

// See `SQLCommonTableExpressionBuilder.tableExpressionGroup`.
@inlinable
public var tableExpressionGroup: SQLCommonTableExpressionGroup? {
get { self.union.tableExpressionGroup }
set { self.union.tableExpressionGroup = newValue }
}

/// Create a new ``SQLUnionBuilder``.
@inlinable
public init(on database: any SQLDatabase, initialQuery: SQLSelect) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Builds ``SQLUpdate`` queries.
public final class SQLUpdateBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLReturningBuilder, SQLColumnUpdateBuilder {
public final class SQLUpdateBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLReturningBuilder, SQLColumnUpdateBuilder, SQLCommonTableExpressionBuilder {
/// An ``SQLUpdate`` containing the complete current state of the builder.
public var update: SQLUpdate

Expand Down Expand Up @@ -33,6 +33,13 @@ public final class SQLUpdateBuilder: SQLQueryBuilder, SQLPredicateBuilder, SQLRe
set { self.update.returning = newValue }
}

// See `SQLCommonTableExpressionBuilder.tableExpressionGroup`.
@inlinable
public var tableExpressionGroup: SQLCommonTableExpressionGroup? {
get { self.update.tableExpressionGroup }
set { self.update.tableExpressionGroup = newValue }
}

/// Create a new ``SQLUpdateBuilder``.
///
/// Use this API directly only if you need to have control over the builder's initial update query. Prefer using
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// expressions in other queries.
///
/// > Important: Despite the use of the term "subquery", this builder does not provide
/// > methods for specifying subquery operators (e.g. `ANY`, `SOME`) or CTE clauses (`WITH`),
/// > methods for specifying subquery operators (e.g. `ANY`, `SOME`),
/// > nor does it enclose its result in grouping parenthesis, as all of these formations are
/// > context-specific and are the purview of builders that conform to this protocol.
///
Expand All @@ -21,7 +21,8 @@ public protocol SQLSubqueryClauseBuilder:
SQLPredicateBuilder,
SQLSecondaryPredicateBuilder,
SQLPartialResultBuilder,
SQLAliasedColumnListBuilder
SQLAliasedColumnListBuilder,
SQLCommonTableExpressionBuilder
{
/// The ``SQLSelect`` query under construction.
var select: SQLSelect { get set }
Expand Down Expand Up @@ -69,6 +70,13 @@ extension SQLSubqueryClauseBuilder {
get { self.select.columns }
set { self.select.columns = newValue }
}

// See `SQLCommonTableExpressionBuilder.tableExpressionGroup`.
@inlinable
public var tableExpressionGroup: SQLCommonTableExpressionGroup? {
get { self.select.tableExpressionGroup }
set { self.select.tableExpressionGroup = newValue }
}
}

// MARK: - Distinct
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/// A clause describing a single Common Table Expressions, which in itws simplest form provides
/// additional data to a primary query in the same way as joining to a subquery.
///
/// > Note: There is no ``SQLDialect`` flag for CTE support, as CTEs are supported by all of the
/// > databases for which first-party drivers exist at the time of this writing (although they are
/// > not available in MySQL 5.7, which is long since EOL and should not be in use by anyone anymore).
public struct SQLCommonTableExpression: SQLExpression {
/// Indicates whether the CTE is recursive, e.g. whether its query is a `UNION` whose second subquery
/// refers to the CTE's own aliased name.
///
/// > Warning: Neither ``SQLCommonTableExpression`` nor the methods of ``SQLCommonTableExpressionBuilder``
/// > validate that a recursive CTE's query takes the proper form, nor that a non-recursive CTE's query
/// > is not self-referential. It is the responsibility of the user to specify the flag accurately. Failure
/// > to do so will result in generating invalid SQL.
public var isRecursive: Bool = false

/// The name used to refer to the CTE's data.
public var alias: any SQLExpression

/// A list of column names yielded by the CTE. May be empty.
public var columns: [any SQLExpression] = []

/// The subquery which yields the CTE's data.
public var query: any SQLExpression

/// Create a new Common Table Expression.
///
/// - Parameters:
/// - alias: Specifies the name to be used to refer to the CTE.
/// - query: The subquery which yields the CTE's data.
public init(alias: some SQLExpression, query: some SQLExpression) {
self.alias = alias
self.query = query
}

// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
/// The ``SQLCommonTableExpression/isRecursive`` flag is not used in this logic. This is not an
/// oversight. CTE syntax requires that `RECURSIVE` be specified as part of the overall `WITH`
/// clause, rather on a per-CTE basis. As such, the recursive flag is handled by the serialization
/// logic of ``SQLCommonTableExpressionGroup``.
$0.append(self.alias)
if !self.columns.isEmpty {
$0.append(SQLGroupExpression(self.columns))
}
if let subqueryExpr = self.query as? SQLSubquery {
$0.append("AS", subqueryExpr)
} else if let subqueryExpr = self.query as? SQLUnionSubquery {
$0.append("AS", subqueryExpr)
} else if let groupExpr = self.query as? SQLGroupExpression {
$0.append("AS", groupExpr)
} else {
$0.append("AS", SQLGroupExpression(self.query))
}
}
}
}

/// A clause representing a group of one or more ``SQLCommonTableExpression``s.
///
/// This expression makes up a complete `WITH` clause in the generated SQL, serving to centralize the
/// serialization logic for such a clause in a single location rather than requiring it to be repeated
/// by every query type that supports CTEs.
public struct SQLCommonTableExpressionGroup: SQLExpression {
/// The list of common table expressions which make up the group.
///
/// Must contain at least one expression. If the list is empty, invalid SQL will be generated.
public var tableExpressions: [any SQLExpression]

public init(tableExpressions: [any SQLExpression]) {
self.tableExpressions = tableExpressions
}

// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
$0.append("WITH")
if self.tableExpressions.contains(where: { ($0 as? SQLCommonTableExpression)?.isRecursive ?? false }) {
$0.append("RECURSIVE")
}
$0.append(SQLList(self.tableExpressions))
}
}
}
5 changes: 5 additions & 0 deletions Sources/SQLKit/Expressions/Queries/SQLDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
///
/// See ``SQLDeleteBuilder``.
public struct SQLDelete: SQLExpression {
/// An optional common table expression group.
public var tableExpressionGroup: SQLCommonTableExpressionGroup?

/// The table containing rows to delete.
public var table: any SQLExpression

Expand All @@ -34,6 +37,8 @@ public struct SQLDelete: SQLExpression {
// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
$0.append(self.tableExpressionGroup)

$0.append("DELETE FROM", self.table)
if let predicate = self.predicate {
$0.append("WHERE", predicate)
Expand Down
4 changes: 4 additions & 0 deletions Sources/SQLKit/Expressions/Queries/SQLInsert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
///
/// See ``SQLInsertBuilder``.
public struct SQLInsert: SQLExpression {
/// An optional common table expression group.
public var tableExpressionGroup: SQLCommonTableExpressionGroup?

/// The table to which rows are to be added.
public var table: any SQLExpression

Expand Down Expand Up @@ -70,6 +73,7 @@ public struct SQLInsert: SQLExpression {
// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
$0.append(self.tableExpressionGroup)
$0.append("INSERT")
$0.append(self.conflictStrategy?.queryModifier(for: $0))
$0.append("INTO", self.table)
Expand Down
4 changes: 4 additions & 0 deletions Sources/SQLKit/Expressions/Queries/SQLSelect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
///
/// See ``SQLSelectBuilder``.
public struct SQLSelect: SQLExpression {
/// An optional common table expression group.
public var tableExpressionGroup: SQLCommonTableExpressionGroup?

/// One or more expessions describing the data to retrieve from the database.
public var columns: [any SQLExpression] = []

Expand Down Expand Up @@ -97,6 +100,7 @@ public struct SQLSelect: SQLExpression {
// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
$0.append(self.tableExpressionGroup)
$0.append("SELECT")
if self.isDistinct {
$0.append("DISTINCT")
Expand Down
5 changes: 5 additions & 0 deletions Sources/SQLKit/Expressions/Queries/SQLUnion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
///
/// See ``SQLUnionBuilder``.
public struct SQLUnion: SQLExpression {
/// An optional common table expression group.
public var tableExpressionGroup: SQLCommonTableExpressionGroup?

/// The required first query of the union.
public var initialQuery: SQLSelect

Expand Down Expand Up @@ -80,6 +83,8 @@ public struct SQLUnion: SQLExpression {
// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement { stmt in
stmt.append(self.tableExpressionGroup)

guard !self.unions.isEmpty else {
/// If no unions are specified, serialize as a plain query even if the dialect would otherwise
/// specify the use of parenthesized subqueries. Ignores orderBys, limit, and offset.
Expand Down
4 changes: 4 additions & 0 deletions Sources/SQLKit/Expressions/Queries/SQLUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
///
/// See ``SQLUpdateBuilder``.
public struct SQLUpdate: SQLExpression {
/// An optional common table expression group.
public var tableExpressionGroup: SQLCommonTableExpressionGroup?

/// The table containing the row(s) to be updated.
public var table: any SQLExpression

Expand Down Expand Up @@ -43,6 +46,7 @@ public struct SQLUpdate: SQLExpression {
// See `SQLExpression.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
serializer.statement {
$0.append(self.tableExpressionGroup)
$0.append("UPDATE", self.table)
$0.append("SET", SQLList(self.values))
if let predicate = self.predicate {
Expand Down
4 changes: 2 additions & 2 deletions Tests/SQLKitTests/SQLCodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func superCase(_ path: [any CodingKey]) -> any CodingKey {
SomeCodingKey(stringValue: path.last!.stringValue.encapitalized)
}

extension SQLLiteral: Equatable {
extension SQLKit.SQLLiteral: Swift.Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.all, .all), (.`default`, .`default`), (.null, .null): return true
Expand All @@ -277,7 +277,7 @@ extension SQLLiteral: Equatable {
}
}

extension SQLBind: Equatable {
extension SQLKit.SQLBind: Swift.Equatable {
// Don't do this. This is horrible.
public static func == (lhs: Self, rhs: Self) -> Bool { (try? JSONEncoder().encode(lhs.encodable) == JSONEncoder().encode(rhs.encodable)) ?? false }
}
Loading