Skip to content

Commit

Permalink
Merge branch 'main' into feature/multirowInsert
Browse files Browse the repository at this point in the history
  • Loading branch information
NeedleInAJayStack committed Nov 8, 2022
2 parents 5d51f3c + 3c5413a commit c5b1f51
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 84 deletions.
72 changes: 72 additions & 0 deletions Sources/SQLKit/Builders/SQLPartialResultBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

public protocol SQLPartialResultBuilder: AnyObject {
/// Zero or more `ORDER BY` clauses.
var orderBys: [SQLExpression] { get set }

/// If set, limits the maximum number of results.
var limit: Int? { get set }

/// If set, offsets the results.
var offset: Int? { get set }
}

// MARK: - Limit/offset

extension SQLPartialResultBuilder {
/// Adds a `LIMIT` clause to the query. If called more than once, the last call wins.
///
/// - Parameter max: Optional maximum limit. If `nil`, any existing limit is removed.
/// - Returns: `self` for chaining.
@discardableResult
public func limit(_ max: Int?) -> Self {
self.limit = max
return self
}

/// Adds a `OFFSET` clause to the query. If called more than once, the last call wins.
///
/// - Parameter max: Optional offset. If `nil`, any existing offset is removed.
/// - Returns: `self` for chaining.
@discardableResult
public func offset(_ n: Int?) -> Self {
self.offset = n
return self
}
}

// MARK: - Order

extension SQLPartialResultBuilder {
/// Adds an `ORDER BY` clause to the query with the specified column and ordering.
///
/// - Parameters:
/// - column: Name of column to sort results by. Appended to any previously added orderings.
/// - direction: The sort direction for the column.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ column: String, _ direction: SQLDirection = .ascending) -> Self {
return self.orderBy(SQLColumn(column), direction)
}


/// Adds an `ORDER BY` clause to the query with the specifed expression and ordering.
///
/// - Parameters:
/// - expression: Expression to sort results by. Appended to any previously added orderings.
/// - direction: An expression describing the sort direction for the ordering expression.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ expression: SQLExpression, _ direction: SQLExpression) -> Self {
return self.orderBy(SQLOrderBy(expression: expression, direction: direction))
}

/// Adds an `ORDER BY` clause to the query using the specified expression.
///
/// - Parameter expression: Expression to sort results by. Appended to any previously added orderings.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ expression: SQLExpression) -> Self {
orderBys.append(expression)
return self
}
}
103 changes: 33 additions & 70 deletions Sources/SQLKit/Builders/SQLSubqeryClauseBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// - Note: The primary motivation for the existence of this protocol is to make it easier
/// to construct `SELECT` queries without specifying a database or providing the
/// `SQLQueryBuilder` and `SQLQueryFetcher` methods in inappropriate contexts.
public protocol SQLSubqueryClauseBuilder: SQLJoinBuilder, SQLPredicateBuilder, SQLSecondaryPredicateBuilder {
public protocol SQLSubqueryClauseBuilder: SQLJoinBuilder, SQLPredicateBuilder, SQLSecondaryPredicateBuilder, SQLPartialResultBuilder {
var select: SQLSelect { get set }
}

Expand All @@ -35,6 +35,23 @@ extension SQLSubqueryClauseBuilder {
}
}

extension SQLSubqueryClauseBuilder {
public var orderBys: [SQLExpression] {
get { self.select.orderBy }
set { self.select.orderBy = newValue }
}

public var limit: Int? {
get { self.select.limit }
set { self.select.limit = newValue }
}

public var offset: Int? {
get { self.select.offset }
set { self.select.offset = newValue }
}
}

// MARK: - Distinct

extension SQLSubqueryClauseBuilder {
Expand Down Expand Up @@ -220,30 +237,6 @@ extension SQLSubqueryClauseBuilder {
}
}

// MARK: - Limit/offset

extension SQLSubqueryClauseBuilder {
/// Adds a `LIMIT` clause to the query. If called more than once, the last call wins.
///
/// - Parameter max: Optional maximum limit. If `nil`, any existing limit is removed.
/// - Returns: `self` for chaining.
@discardableResult
public func limit(_ max: Int?) -> Self {
self.select.limit = max
return self
}

/// Adds a `OFFSET` clause to the query. If called more than once, the last call wins.
///
/// - Parameter max: Optional offset. If `nil`, any existing offset is removed.
/// - Returns: `self` for chaining.
@discardableResult
public func offset(_ n: Int?) -> Self {
self.select.offset = n
return self
}
}

// MARK: - Group By

extension SQLSubqueryClauseBuilder {
Expand All @@ -267,72 +260,42 @@ extension SQLSubqueryClauseBuilder {
}
}

// MARK: - Order

extension SQLSubqueryClauseBuilder {
/// Adds an `ORDER BY` clause to the query with the specified column and ordering.
///
/// - Parameters:
/// - column: Name of column to sort results by. Appended to any previously added orderings.
/// - direction: The sort direction for the column.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ column: String, _ direction: SQLDirection = .ascending) -> Self {
return self.orderBy(SQLColumn(column), direction)
}


/// Adds an `ORDER BY` clause to the query with the specifed expression and ordering.
///
/// - Parameters:
/// - expression: Expression to sort results by. Appended to any previously added orderings.
/// - direction: An expression describing the sort direction for the ordering expression.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ expression: SQLExpression, _ direction: SQLExpression) -> Self {
return self.orderBy(SQLOrderBy(expression: expression, direction: direction))
}

/// Adds an `ORDER BY` clause to the query using the specified expression.
///
/// - Parameter expression: Expression to sort results by. Appended to any previously added orderings.
/// - Returns: `self` for chaining.
@discardableResult
public func orderBy(_ expression: SQLExpression) -> Self {
select.orderBy.append(expression)
return self
}
}

// MARK: - Locking

extension SQLSubqueryClauseBuilder {
/// Adds a locking clause to this query. If called more than once, the last call wins.
///
/// ```swift
/// db.select()...for(.update)
/// db.select()...for(.share)
/// ```
///
/// Also called locking reads, the `SELECT ... FOR UPDATE` syntax locks all selected rows
/// for the duration of the current transaction. How the rows are locked depends on the
/// specific expression supplied and the underlying database implementation.
/// Also referred to as locking or "consistent" reads, the locking clause syntax locks
/// all selected rows for the duration of the current transaction with a type of lock
/// determined by the specific locking clause and the underlying database's support for
/// this construct.
///
/// - Parameter lockingClause: The type of lock to obtain.
/// - Warning: If the database in use does not support locking reads, the locking clause
/// will be silently ignored regardless of its value.
///
/// - Parameter lockingClause: The type of lock to obtain. See ``SQLLockingClause``.
/// - Returns: `self` for chaining.
@discardableResult
public func `for`(_ lockingClause: SQLLockingClause) -> Self {
return self.lockingClause(lockingClause)
}

/// Adds a locking clause to this query. If called more than once, the last call wins.
/// Adds a locking clause to this query as specified by an arbitrary ``SQLExpression``.
/// If called more than once, the last call wins.
///
/// ```swift
/// db.select()...lockingClause(...)
/// ```
///
/// Also called locking reads, the `SELECT ... FOR UPDATE` syntax locks all selected rows
/// for the duration of the current transaction. How the rows are locked depends on the
/// specific expression supplied and the underlying database implementation.
/// Also referred to as locking or "consistent" reads, the locking clause syntax locks
/// all selected rows for the duration of the current transaction with a type of lock
/// determined by the specific locking clause and the underlying database's support for
/// this construct.
///
/// - Note: This method allows providing an arbitrary SQL expression as the locking clause.
///
Expand Down
19 changes: 18 additions & 1 deletion Sources/SQLKit/Builders/SQLUnionBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher {
public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher, SQLPartialResultBuilder {
public var query: SQLExpression { self.union }

public var union: SQLUnion
Expand Down Expand Up @@ -40,6 +40,23 @@ public final class SQLUnionBuilder: SQLQueryBuilder, SQLQueryFetcher {
}
}

extension SQLUnionBuilder {
public var orderBys: [SQLExpression] {
get { self.union.orderBys }
set { self.union.orderBys = newValue }
}

public var limit: Int? {
get { self.union.limit }
set { self.union.limit = newValue }
}

public var offset: Int? {
get { self.union.offset }
set { self.union.offset = newValue }
}
}

extension SQLDatabase {
public func union(_ predicate: (SQLSelectBuilder) -> SQLSelectBuilder) -> SQLUnionBuilder {
return SQLUnionBuilder(on: self, initialQuery: predicate(.init(on: self)).select)
Expand Down
21 changes: 11 additions & 10 deletions Sources/SQLKit/Query/SQLLockingClause.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
/// General locking expressions for a SQL locking clause.
/// General locking expressions for a SQL locking clause. The actual locking clause syntax
/// for any given SQL dialect is defined by the dialect.
///
/// SELECT ... FOR UPDATE
///
/// See `SQLSelectBuilder.for` and `SQLSelect.lockingClause`.
/// See ``SQLSubqueryClauseBuilder/for(_:)`` and ``SQLSelect/lockingClause``.
public enum SQLLockingClause: SQLExpression {
/// `UPDATE`
/// Request an exclusive "writer" lock.
case update

/// `SHARE`
/// Request a shared "reader" lock.
case share

/// See `SQLExpression`.
/// See ``SQLExpression/serialize(to:)``.
public func serialize(to serializer: inout SQLSerializer) {
switch self {
case .share:
serializer.write("FOR SHARE")
case .update:
serializer.write("FOR UPDATE")
serializer.statement {
switch self {
case .share: $0.append($0.dialect.sharedSelectLockExpression)
case .update: $0.append($0.dialect.exclusiveSelectLockExpression)
}
}
}
}
25 changes: 25 additions & 0 deletions Sources/SQLKit/Query/SQLUnion.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
public struct SQLUnion: SQLExpression {
public var initialQuery: SQLSelect
public var unions: [(SQLUnionJoiner, SQLSelect)]

/// Zero or more `ORDER BY` clauses.
public var orderBys: [SQLExpression]

/// If set, limits the maximum number of results.
public var limit: Int?

/// If set, offsets the results.
public var offset: Int?

public init(initialQuery: SQLSelect, unions: [(SQLUnionJoiner, SQLSelect)] = []) {
self.initialQuery = initialQuery
self.unions = unions
self.limit = nil
self.offset = nil
self.orderBys = []
}

public mutating func add(_ query: SQLSelect, all: Bool) {
Expand Down Expand Up @@ -34,6 +46,19 @@ public struct SQLUnion: SQLExpression {
statement.append(joiner)
appendQuery(query)
}

if !self.orderBys.isEmpty {
statement.append("ORDER BY")
statement.append(SQLList(self.orderBys))
}
if let limit = self.limit {
statement.append("LIMIT")
statement.append(limit.description)
}
if let offset = self.offset {
statement.append("OFFSET")
statement.append(offset.description)
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/SQLKit/SQLDialect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ public protocol SQLDialect {
///
/// Defaults to `[.union, .unionAll]`.
var unionFeatures: SQLUnionFeatures { get }

/// A serialization for ``SQLLockingClause/share``, representing a request for a shared "reader"
/// lock on rows retrieved by a `SELECT` query. A `nil` value means the database doesn't
/// support shared locking requests, which causes the locking clause to be silently ignored.
var sharedSelectLockExpression: SQLExpression? { get }

/// A serialization for ``SQLLockingClause/update``, representing a request for an exclusive
/// "writer" lock on rows retrieved by a `SELECT` query. A `nil` value means the database doesn't
/// support exclusive locking requests, which causes the locking clause to be silently ignored.
var exclusiveSelectLockExpression: SQLExpression? { get }

}

/// Controls `ALTER TABLE` syntax.
Expand Down Expand Up @@ -309,4 +320,6 @@ extension SQLDialect {
public func normalizeSQLConstraint(identifier: SQLExpression) -> SQLExpression { identifier }
public var upsertSyntax: SQLUpsertSyntax { .unsupported }
public var unionFeatures: SQLUnionFeatures { [.union, .unionAll] }
public var sharedSelectLockExpression: SQLExpression? { nil }
public var exclusiveSelectLockExpression: SQLExpression? { nil }
}
5 changes: 5 additions & 0 deletions Sources/SQLKit/SQLStatement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public struct SQLStatement: SQLExpression {
public mutating func append(_ part: SQLExpression) {
self.parts.append(part)
}

/// Add an ``SQLExpression`` of any kind to the output, but only if it isn't `nil`.
public mutating func append(_ maybePart: SQLExpression?) {
maybePart.map { self.append($0) }
}

// See `SQLSerializer.serialize(to:)`.
public func serialize(to serializer: inout SQLSerializer) {
Expand Down

0 comments on commit c5b1f51

Please sign in to comment.