diff --git a/Sources/SQLKit/Builders/Implementations/SQLDeleteBuilder.swift b/Sources/SQLKit/Builders/Implementations/SQLDeleteBuilder.swift
index 93e2f413..5912bb5b 100644
--- a/Sources/SQLKit/Builders/Implementations/SQLDeleteBuilder.swift
+++ b/Sources/SQLKit/Builders/Implementations/SQLDeleteBuilder.swift
@@ -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
 
@@ -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) {
diff --git a/Sources/SQLKit/Builders/Implementations/SQLInsertBuilder.swift b/Sources/SQLKit/Builders/Implementations/SQLInsertBuilder.swift
index 5ca04987..cb16b50e 100644
--- a/Sources/SQLKit/Builders/Implementations/SQLInsertBuilder.swift
+++ b/Sources/SQLKit/Builders/Implementations/SQLInsertBuilder.swift
@@ -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
     
@@ -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) {
diff --git a/Sources/SQLKit/Builders/Implementations/SQLUnionBuilder.swift b/Sources/SQLKit/Builders/Implementations/SQLUnionBuilder.swift
index 16fa00d0..2e72cd82 100644
--- a/Sources/SQLKit/Builders/Implementations/SQLUnionBuilder.swift
+++ b/Sources/SQLKit/Builders/Implementations/SQLUnionBuilder.swift
@@ -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
 
@@ -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) {
diff --git a/Sources/SQLKit/Builders/Implementations/SQLUpdateBuilder.swift b/Sources/SQLKit/Builders/Implementations/SQLUpdateBuilder.swift
index 502cfda7..1acc493e 100644
--- a/Sources/SQLKit/Builders/Implementations/SQLUpdateBuilder.swift
+++ b/Sources/SQLKit/Builders/Implementations/SQLUpdateBuilder.swift
@@ -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
     
@@ -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
diff --git a/Sources/SQLKit/Builders/Prototypes/SQLCommonTableExpressionBuilder.swift b/Sources/SQLKit/Builders/Prototypes/SQLCommonTableExpressionBuilder.swift
new file mode 100644
index 00000000..49c52464
--- /dev/null
+++ b/Sources/SQLKit/Builders/Prototypes/SQLCommonTableExpressionBuilder.swift
@@ -0,0 +1,323 @@
+/// Common definitions for query builders which support Common Table Expressions.
+public protocol SQLCommonTableExpressionBuilder: AnyObject {
+    /// An optional group of common table expressions to include in the query under construction.
+    var tableExpressionGroup: SQLCommonTableExpressionGroup? { get set }
+}
+
+extension SQLCommonTableExpressionBuilder {
+    // MARK: - String name, string columns
+    
+    /// Specify a subquery to include as a common table expression, for use elsewhere in the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with("c", columns: ["a"], as: SQLSubquery.select {$0
+    ///         .column("x")
+    ///         .from("table3")
+    ///     })
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("a", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a non-recursive CTE's query is not self-referential. It is the responsibility of the user to invoke
+    /// > the appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE, usually a `SELECT` query.
+    @inlinable
+    @discardableResult
+    public func with(_ name: some StringProtocol, columns: [String], as query: some SQLExpression) -> Self {
+        self.with(name, columns: columns.map(SQLIdentifier.init(_:)), as: query)
+    }
+
+    /// Specify a subquery to include as a _recursive_ common table expression, for use elsewhere in
+    /// the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with(recursive: "c", columns: ["n"], as: SQLSubquery
+    ///         .union { $0.column(SQLBind("1"), as: "n") }
+    ///         .union(all: { $0
+    ///             .column(SQLBinaryExpression("n", .add, 1))
+    ///             .from("c").where("n", .lessThan, 3)
+    ///         }).finish())
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("n", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a recursive CTE's query takes the proper form. It is the responsibility of the user to invoke the
+    /// > appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE. For a recursive CTE, this must be an
+    ///     expression representing at least one `SELECT` statement which does _not_ refer to the CTE and at least
+    ///     one `UNION ALL` or `UNION DISTINCT` clause terminating with a `SELECT` statement which explicitly refers
+    ///     to the CTE itself.
+    @inlinable
+    @discardableResult
+    public func with(recursive name: some StringProtocol, columns: [String], as query: some SQLExpression) -> Self {
+        self.with(recursive: name, columns: columns.map(SQLIdentifier.init(_:)), as: query)
+    }
+
+    // MARK: - String name, expression columns
+    
+    /// Specify a subquery to include as a common table expression, for use elsewhere in the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with("c", columns: ["a"], as: SQLSubquery.select {$0
+    ///         .column("x")
+    ///         .from("table3")
+    ///     })
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("a", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a non-recursive CTE's query is not self-referential. It is the responsibility of the user to invoke
+    /// > the appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE, usually a `SELECT` query.
+    @inlinable
+    @discardableResult
+    public func with(_ name: some StringProtocol, columns: [any SQLExpression] = [], as query: some SQLExpression) -> Self {
+        self.with(SQLIdentifier(String(name)), columns: columns, as: query)
+    }
+
+    /// Specify a subquery to include as a _recursive_ common table expression, for use elsewhere in
+    /// the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with(recursive: "c", columns: ["n"], as: SQLSubquery
+    ///         .union { $0.column(SQLBind("1"), as: "n") }
+    ///         .union(all: { $0
+    ///             .column(SQLBinaryExpression("n", .add, 1))
+    ///             .from("c").where("n", .lessThan, 3)
+    ///         }).finish())
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("n", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a recursive CTE's query takes the proper form. It is the responsibility of the user to invoke the
+    /// > appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE. For a recursive CTE, this must be an
+    ///     expression representing at least one `SELECT` statement which does _not_ refer to the CTE and at least
+    ///     one `UNION ALL` or `UNION DISTINCT` clause terminating with a `SELECT` statement which explicitly refers
+    ///     to the CTE itself.
+    @inlinable
+    @discardableResult
+    public func with(recursive name: some StringProtocol, columns: [any SQLExpression] = [], as query: some SQLExpression) -> Self {
+        self.with(recursive: SQLIdentifier(String(name)), columns: columns, as: query)
+    }
+
+    // MARK: - Expression name, string columns
+    
+    /// Specify a subquery to include as a common table expression, for use elsewhere in the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with("c", columns: ["a"], as: SQLSubquery.select {$0
+    ///         .column("x")
+    ///         .from("table3")
+    ///     })
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("a", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a non-recursive CTE's query is not self-referential. It is the responsibility of the user to invoke
+    /// > the appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE, usually a `SELECT` query.
+    @inlinable
+    @discardableResult
+    public func with(_ name: some SQLExpression, columns: [String], as query: some SQLExpression) -> Self {
+        self.with(name, columns: columns.map(SQLIdentifier.init(_:)), as: query)
+    }
+
+    /// Specify a subquery to include as a _recursive_ common table expression, for use elsewhere in
+    /// the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with(recursive: "c", columns: ["n"], as: SQLSubquery
+    ///         .union { $0.column(SQLBind("1"), as: "n") }
+    ///         .union(all: { $0
+    ///             .column(SQLBinaryExpression("n", .add, 1))
+    ///             .from("c").where("n", .lessThan, 3)
+    ///         }).finish())
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("n", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a recursive CTE's query takes the proper form. It is the responsibility of the user to invoke the
+    /// > appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE. For a recursive CTE, this must be an
+    ///     expression representing at least one `SELECT` statement which does _not_ refer to the CTE and at least
+    ///     one `UNION ALL` or `UNION DISTINCT` clause terminating with a `SELECT` statement which explicitly refers
+    ///     to the CTE itself.
+    @inlinable
+    @discardableResult
+    public func with(recursive name: some SQLExpression, columns: [String], as query: some SQLExpression) -> Self {
+        self.with(recursive: name, columns: columns.map(SQLIdentifier.init(_:)), as: query)
+    }
+
+    // MARK: - Expression name, expression columns
+    
+    /// Specify a subquery to include as a common table expression, for use elsewhere in the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with("c", columns: ["a"], as: SQLSubquery.select {$0
+    ///         .column("x")
+    ///         .from("table3")
+    ///     })
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("a", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ validate
+    /// > that a non-recursive CTE's query is not self-referential. It is the responsibility of the user to invoke
+    /// > the appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE, usually a `SELECT` query.
+    @inlinable
+    @discardableResult
+    public func with(_ name: some SQLExpression, columns: [any SQLExpression] = [], as query: some SQLExpression) -> Self {
+        self.with(isRecursive: false, name: name, columns: columns, as: query)
+    }
+
+    /// Specify a subquery to include as a _recursive_ common table expression, for use elsewhere in
+    /// the overall query.
+    ///
+    /// Example usage:
+    /// ```swift
+    /// try await sqlDatabase.update("table1")
+    ///     .with(recursive: "c", columns: ["n"], as: SQLSubquery
+    ///         .union { $0.column(SQLBind("1"), as: "n") }
+    ///         .union(all: { $0
+    ///             .column(SQLBinaryExpression("n", .add, 1))
+    ///             .from("c").where("n", .lessThan, 3)
+    ///         }).finish())
+    ///     .set("foo", to: "bar")
+    ///     .where("foo", .equal, SQLColumn("n", table: "c"))
+    ///     .run()
+    /// ```
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_
+    /// > validate that a recursive CTE's query takes the proper form. It is the responsibility of the user to
+    /// > invoke the appropriate variant of this method. Failure to do so will result in generating invalid SQL.
+    ///
+    /// - Parameters:
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE. For a recursive CTE, this must be an
+    ///     expression representing at least one `SELECT` statement which does _not_ refer to the CTE and at least
+    ///     one `UNION ALL` or `UNION DISTINCT` clause terminating with a `SELECT` statement which explicitly refers
+    ///     to the CTE itself.
+    @inlinable
+    @discardableResult
+    public func with(recursive name: some SQLExpression, columns: [any SQLExpression] = [], as query: some SQLExpression) -> Self {
+        self.with(isRecursive: true, name: name, columns: columns, as: query)
+    }
+
+    // MARK: - Funnel
+
+    /// Specify a potentially-recursive common table expression for use elsewhere in a query.
+    ///
+    /// This is the common "funnel" method invoked by all other methods provided by
+    /// ``SQLCommonTableExpressionBuilder``. Most users will not need to call this method directly.
+    ///
+    /// See ``with(_:columns:as)`` and ``with(recursive:_:columns:as:)`` for usage examples.
+    ///
+    /// > Warning: As with ``SQLCommonTableExpression``, ``SQLCommonTableExpressionBuilder`` does _NOT_ 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.
+    ///
+    /// - Parameters:
+    ///   - isRecursive: Specifies whether or not the CTE is recursive.
+    ///   - name: The name to assign to the query's results.
+    ///   - columns: An optional list of unqualified column names to use for referencing the query's results.
+    ///     If no column names are provided, the names are inferred from the query. If column names are provided,
+    ///     the number of names provided must match the number of columns returned by the query.
+    ///   - query: An expression which provides the contents of the CTE. If the CTE is recursive, this must be an
+    ///     expression representing at least one `SELECT` statement which does _not_ refer to the CTE and at least
+    ///     one `UNION ALL` or `UNION DISTINCT` clause terminating with a `SELECT` statement which explicitly refers
+    ///     to the CTE itself.
+    @inlinable
+    @discardableResult
+    public func with(
+        isRecursive: Bool,
+        name: some SQLExpression,
+        columns: [any SQLExpression] = [],
+        as query: some SQLExpression
+    ) -> Self {
+        var expression = SQLCommonTableExpression(alias: name, query: query)
+        expression.isRecursive = isRecursive
+        expression.columns = columns
+        
+        if self.tableExpressionGroup != nil {
+            self.tableExpressionGroup?.tableExpressions.append(expression)
+        } else {
+            self.tableExpressionGroup = .init(tableExpressions: [expression])
+        }
+        return self
+    }
+}
diff --git a/Sources/SQLKit/Builders/Prototypes/SQLSubqueryClauseBuilder.swift b/Sources/SQLKit/Builders/Prototypes/SQLSubqueryClauseBuilder.swift
index 844185a3..3f183e9f 100644
--- a/Sources/SQLKit/Builders/Prototypes/SQLSubqueryClauseBuilder.swift
+++ b/Sources/SQLKit/Builders/Prototypes/SQLSubqueryClauseBuilder.swift
@@ -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.
 ///
@@ -21,7 +21,8 @@ public protocol SQLSubqueryClauseBuilder:
     SQLPredicateBuilder,
     SQLSecondaryPredicateBuilder,
     SQLPartialResultBuilder,
-    SQLAliasedColumnListBuilder
+    SQLAliasedColumnListBuilder,
+    SQLCommonTableExpressionBuilder
 {
     /// The ``SQLSelect`` query under construction.
     var select: SQLSelect { get set }
@@ -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
diff --git a/Sources/SQLKit/Expressions/Clauses/SQLCommonTableExpression.swift b/Sources/SQLKit/Expressions/Clauses/SQLCommonTableExpression.swift
new file mode 100644
index 00000000..0629c0d7
--- /dev/null
+++ b/Sources/SQLKit/Expressions/Clauses/SQLCommonTableExpression.swift
@@ -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))
+        }
+    }
+}
diff --git a/Sources/SQLKit/Expressions/Queries/SQLDelete.swift b/Sources/SQLKit/Expressions/Queries/SQLDelete.swift
index c7b234da..535bea9d 100644
--- a/Sources/SQLKit/Expressions/Queries/SQLDelete.swift
+++ b/Sources/SQLKit/Expressions/Queries/SQLDelete.swift
@@ -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
     
@@ -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)
diff --git a/Sources/SQLKit/Expressions/Queries/SQLInsert.swift b/Sources/SQLKit/Expressions/Queries/SQLInsert.swift
index 3389f6f0..220f6ab9 100644
--- a/Sources/SQLKit/Expressions/Queries/SQLInsert.swift
+++ b/Sources/SQLKit/Expressions/Queries/SQLInsert.swift
@@ -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
     
@@ -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)
diff --git a/Sources/SQLKit/Expressions/Queries/SQLSelect.swift b/Sources/SQLKit/Expressions/Queries/SQLSelect.swift
index 10adf8bc..6d51d44c 100644
--- a/Sources/SQLKit/Expressions/Queries/SQLSelect.swift
+++ b/Sources/SQLKit/Expressions/Queries/SQLSelect.swift
@@ -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] = []
     
@@ -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")
diff --git a/Sources/SQLKit/Expressions/Queries/SQLUnion.swift b/Sources/SQLKit/Expressions/Queries/SQLUnion.swift
index c15c3168..f1f37111 100644
--- a/Sources/SQLKit/Expressions/Queries/SQLUnion.swift
+++ b/Sources/SQLKit/Expressions/Queries/SQLUnion.swift
@@ -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
     
@@ -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.
diff --git a/Sources/SQLKit/Expressions/Queries/SQLUpdate.swift b/Sources/SQLKit/Expressions/Queries/SQLUpdate.swift
index 7709e674..f6a79951 100644
--- a/Sources/SQLKit/Expressions/Queries/SQLUpdate.swift
+++ b/Sources/SQLKit/Expressions/Queries/SQLUpdate.swift
@@ -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
     
@@ -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 {
diff --git a/Tests/SQLKitTests/SQLCodingTests.swift b/Tests/SQLKitTests/SQLCodingTests.swift
index 32de909a..1058d149 100644
--- a/Tests/SQLKitTests/SQLCodingTests.swift
+++ b/Tests/SQLKitTests/SQLCodingTests.swift
@@ -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
@@ -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 }
 }
diff --git a/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift b/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift
new file mode 100644
index 00000000..24adcd4a
--- /dev/null
+++ b/Tests/SQLKitTests/SQLCommonTableExpressionTests.swift
@@ -0,0 +1,303 @@
+import SQLKit
+import XCTest
+
+final class SQLCommonTableExpressionTests: XCTestCase {
+    var db = TestDatabase()
+
+    override class func setUp() {
+        XCTAssert(isLoggingConfigured)
+    }
+    
+    func testSelectQueryWithCTE() {
+        XCTAssertSerialization(
+            of: self.db.select().with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.select().with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).column(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+    }
+
+    func testUpdateQueryWithCTE() {
+        XCTAssertSerialization(
+            of: self.db.update("t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.update("t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).set("c", to: SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) UPDATE ``t`` SET ``c`` = ``a``.``b``"
+        )
+    }
+
+    func testInsertQueryWithCTE() {
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.insert(into: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).columns("c").values(SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) INSERT INTO ``t`` (``c``) VALUES (``a``.``b``)"
+        )
+    }
+
+    func testDeleteQueryWithCTE() {
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.delete(from: "t").with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).where("c", .equal, SQLColumn("b", table: "a")),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) DELETE FROM ``t`` WHERE ``c`` = ``a``.``b``"
+        )
+    }
+
+    func testUnionQueryWithCTE() {
+        self.db._dialect.unionFeatures = [.union, .unionAll, .intersect, .intersectAll, .except, .exceptAll, .parenthesizedSubqueries]
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with("a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(recursive: "a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(recursive: "a", columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+
+        XCTAssertSerialization(
+            of: self.db.union { $0 }.with(recursive: SQLIdentifier("a"), columns: [SQLIdentifier("b")], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) }).union(all: { $0.column(SQLColumn("b", table: "a")) }),
+            is: "WITH RECURSIVE ``a`` (``b``) AS (SELECT 1) (SELECT) UNION ALL (SELECT ``a``.``b``)"
+        )
+    }
+    
+    func testMultipleCTEs() {
+        XCTAssertSerialization(
+            of: self.db.select()
+                .with("a", columns: ["b"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) })
+                .with("d", columns: ["e"], as: SQLSubquery.select { $0.column(SQLLiteral.numeric("1")) })
+                .column(SQLColumn("b", table: "a")),
+            is: "WITH ``a`` (``b``) AS (SELECT 1), ``d`` (``e``) AS (SELECT 1) SELECT ``a``.``b``"
+        )
+    }
+    
+    func testCodeCoverage() {
+        var query = self.db.select().column(SQLColumn("b", table: "a")).select
+        query.tableExpressionGroup = .init(tableExpressions: [
+            SQLCommonTableExpression(alias: SQLIdentifier("x"), query: SQLRaw("VALUES(``1``)")),
+            SQLCommonTableExpression(alias: SQLIdentifier("x"), query: SQLGroupExpression(SQLRaw("VALUES(``1``)"))),
+            SQLGroupExpression(SQLRaw("FOO"))
+        ])
+        
+        XCTAssertSerialization(of: self.db.raw("\(query)"), is: "WITH ``x`` AS (VALUES(``1``)), ``x`` AS (VALUES(``1``)), (FOO) SELECT ``a``.``b``")
+    }
+    
+    func testMoreRealisticCTEs() {
+        // Simple sub-SELECT avoidance
+        // Taken from https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-SELECT
+        self.db._dialect.identifierQuote = SQLRaw("\"")
+        XCTAssertSerialization(
+            of: self.db.select()
+                .with("regional_sales", as: SQLSubquery.select { $0
+                    .column("region").column(SQLFunction("SUM", args: SQLColumn("amount")), as: "total_sales").from("orders").groupBy("region")
+                })
+                .with("top_regions", as: SQLSubquery.select { $0
+                    .column("region").from("regional_sales")
+                    .where("total_sales", .greaterThan, SQLSubquery.select { $0
+                        .column(SQLBinaryExpression(SQLFunction("SUM", args: SQLIdentifier("total_sales")), .divide, SQLLiteral.numeric("10")))
+                        .from("regional_sales")
+                    })
+                })
+                .columns("region", "product")
+                .column(SQLFunction("SUM", args: SQLColumn("quantity")), as: "product_units")
+                .column(SQLFunction("SUM", args: SQLColumn("amount")), as: "product_sales")
+                .from("orders").where("region", .in, SQLSubquery.select { $0.column("region").from("top_regions") }).groupBy("region").groupBy("product"),
+            is: """
+                WITH
+                "regional_sales" AS (SELECT "region", SUM("amount") AS "total_sales" FROM "orders" GROUP BY "region"),
+                "top_regions" AS (SELECT "region" FROM "regional_sales" WHERE "total_sales" > (SELECT SUM("total_sales") / 10 FROM "regional_sales"))
+                SELECT "region", "product", SUM("quantity") AS "product_units", SUM("amount") AS "product_sales"
+                FROM "orders"
+                WHERE "region" IN (SELECT "region" FROM "top_regions")
+                GROUP BY "region", "product"
+                """.replacing("\n", with: " ")
+        )
+        
+        // Fibonacci series generator
+        // Taken from https://dev.mysql.com/doc/refman/8.4/en/with.html#common-table-expressions-recursive-fibonacci-series
+        self.db._dialect.identifierQuote = SQLRaw("`")
+        self.db._dialect.unionFeatures = [.union, .unionAll]
+        XCTAssertSerialization(
+            of: self.db.select()
+                .with(recursive: "fibonacci", columns: ["n", "fib_n", "next_fib_n"], as: SQLSubquery.union { $0
+                    .columns(SQLLiteral.numeric("1"), SQLLiteral.numeric("0"), SQLLiteral.numeric("1"))
+                }.union(all: { $0
+                    .column(SQLBinaryExpression(SQLColumn("n"), .add, SQLLiteral.numeric("1")))
+                    .column("next_fib_n")
+                    .column(SQLBinaryExpression(SQLColumn("fib_n"), .add, SQLColumn("next_fib_n")))
+                    .from("fibonacci")
+                    .where("n", .lessThan, SQLLiteral.numeric("10"))
+                })
+                .finish())
+                .column(SQLLiteral.all)
+                .from("fibonacci"),
+            is: """
+                WITH RECURSIVE `fibonacci` (`n`, `fib_n`, `next_fib_n`) AS (
+                    SELECT 1, 0, 1
+                    UNION ALL
+                    SELECT `n` + 1, `next_fib_n`, `fib_n` + `next_fib_n` FROM `fibonacci` WHERE `n` < 10
+                )
+                SELECT * FROM `fibonacci`
+                """.replacing("    ", with: "").replacing("(\n", with: "(").replacing("\n)", with: ")").replacing("\n", with: " ")
+        )
+    }
+}
diff --git a/Tests/SQLKitTests/TestMocks.swift b/Tests/SQLKitTests/TestMocks.swift
index c1276045..ae9405f1 100644
--- a/Tests/SQLKitTests/TestMocks.swift
+++ b/Tests/SQLKitTests/TestMocks.swift
@@ -138,7 +138,7 @@ struct GenericDialect: SQLDialect {
     }
 }
 
-extension SQLDataType: Equatable {
+extension SQLKit.SQLDataType: Swift.Equatable {
     public static func == (lhs: Self, rhs: Self) -> Bool {
         switch (lhs, rhs) {
             case (.bigint, .bigint), (.blob, .blob), (.int, .int), (.real, .real),
