Skip to content

Commit

Permalink
Merge pull request #365 from groue/GRDB3-DelayedOrdering
Browse files Browse the repository at this point in the history
Delayed Request Ordering
  • Loading branch information
groue authored Jun 6, 2018
2 parents 2556fd0 + 018fac8 commit ff69d3f
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 129 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ It comes with new features, but also a few breaking changes, and a set of update
- Request derivation protocols ([#329](https://github.com/groue/GRDB.swift/pull/329)).
- Preliminary Linux support for the main framework ([#354](https://github.com/groue/GRDB.swift/pull/354)).
- Automatic table name generation ([#355](https://github.com/groue/GRDB.swift/pull/355)).
- Delayed Request Ordering ([#365](https://github.com/groue/GRDB.swift/pull/365)).


### Breaking Changes
Expand Down
8 changes: 8 additions & 0 deletions GRDB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@
565490E41D5AE252005622CB /* TableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560D92461C672C4B00F4F92B /* TableRecord.swift */; };
56553C101C3E906C00522B5C /* GRDBTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */; };
56553C131C3E906C00522B5C /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* GRDB.framework */; };
5656BF4F20C723E300F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF4E20C723E300F98521 /* QueryOrdering.swift */; };
5656BF5020C723E300F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF4E20C723E300F98521 /* QueryOrdering.swift */; };
5656BF5120C723E300F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF4E20C723E300F98521 /* QueryOrdering.swift */; };
5657AAB91D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; };
5657AABC1D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; };
5657AB0F1D10899D006283EF /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; };
Expand Down Expand Up @@ -892,6 +895,7 @@
5653EC0B2098738B00F46237 /* SQLGenerationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLGenerationContext.swift; sourceTree = "<group>"; };
565490A01D5A4798005622CB /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; };
56553C181C3E906C00522B5C /* GRDBOSXCrashTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBOSXCrashTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5656BF4E20C723E300F98521 /* QueryOrdering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryOrdering.swift; sourceTree = "<group>"; };
5657AAB81D107001006283EF /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = "<group>"; };
5657AB0E1D10899D006283EF /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
5657AB2F1D108BA9006283EF /* FoundationDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationDataTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1374,6 +1378,7 @@
5605F18C1C6B1A8700235C62 /* QueryInterfaceQuery.swift */,
56300B6F1C53F592005A543B /* QueryInterfaceRequest.swift */,
5653EB242094A14400F46237 /* QueryInterfaceRequest+Association.swift */,
5656BF4E20C723E300F98521 /* QueryOrdering.swift */,
5616AAF0207CD45E00AC3664 /* RequestProtocols.swift */,
5605F1891C6B1A8700235C62 /* SQLCollatedExpression.swift */,
566475991D97D8A000FF74B8 /* SQLCollection.swift */,
Expand Down Expand Up @@ -2308,6 +2313,7 @@
566475C01D981AD200FF74B8 /* SQLSpecificExpressible+QueryInterface.swift in Sources */,
56CEB51F1EAA328900BFAF62 /* FTS5+QueryInterface.swift in Sources */,
565490E21D5AE252005622CB /* Record.swift in Sources */,
5656BF5120C723E300F98521 /* QueryOrdering.swift in Sources */,
565490C11D5AE236005622CB /* FetchRequest.swift in Sources */,
5698AD1C1DAAD17F0056AF8C /* FTS5Tokenizer.swift in Sources */,
56CEB5171EAA324B00BFAF62 /* FTS3+QueryInterfaceRequest.swift in Sources */,
Expand Down Expand Up @@ -2514,6 +2520,7 @@
C96C0F2C2084A459006B2981 /* SQLiteDateParser.swift in Sources */,
5605F1681C672E4000235C62 /* NSNumber.swift in Sources */,
56CEB5041EAA2F4D00BFAF62 /* FTS4.swift in Sources */,
5656BF5020C723E300F98521 /* QueryOrdering.swift in Sources */,
5605F1741C672E4000235C62 /* StandardLibrary.swift in Sources */,
560D92481C672C4B00F4F92B /* PersistableRecord.swift in Sources */,
560D92431C672C3E00F4F92B /* StatementColumnConvertible.swift in Sources */,
Expand Down Expand Up @@ -2915,6 +2922,7 @@
566475BA1D981AD200FF74B8 /* SQLSpecificExpressible+QueryInterface.swift in Sources */,
5653EB0920944C7C00F46237 /* Association.swift in Sources */,
56A238851B9C75030082EB20 /* DatabaseValue.swift in Sources */,
5656BF4F20C723E300F98521 /* QueryOrdering.swift in Sources */,
5671FC201DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift in Sources */,
56A238A41B9C753B0082EB20 /* Record.swift in Sources */,
56CEB5451EAA359A00BFAF62 /* Column.swift in Sources */,
Expand Down
10 changes: 5 additions & 5 deletions GRDB/QueryInterface/Association/Association.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ extension Association {
return mapRequest { $0.filter(predicate) }
}

/// Creates an association with the provided *orderings*.
/// Creates an association with the provided *orderings promise*.
///
/// struct Player: TableRecord {
/// static let team = belongsTo(Team.self)
Expand All @@ -110,7 +110,7 @@ extension Association {
/// // FROM player
/// // JOIN team ON team.id = player.teamId
/// // ORDER BY team.name
/// let association = Player.team.order(Column("name"))
/// let association = Player.team.order { _ in [Column("name")] }
/// var request = Player.including(required: association)
///
/// Any previous ordering is replaced:
Expand All @@ -120,11 +120,11 @@ extension Association {
/// // JOIN team ON team.id = player.teamId
/// // ORDER BY team.name
/// let association = Player.team
/// .order(Column("color"))
/// .order{ _ in [Column("color")] }
/// .reversed()
/// .order(Column("name"))
/// .order{ _ in [Column("name")] }
/// var request = Player.including(required: association)
public func order(_ orderings: [SQLOrderingTerm]) -> Self {
public func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> Self {
return mapRequest { $0.order(orderings) }
}

Expand Down
2 changes: 1 addition & 1 deletion GRDB/QueryInterface/Association/AssociationQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension AssociationQuery {
return query
}

func order(_ orderings: [SQLOrderingTerm]) -> AssociationQuery {
func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> AssociationQuery {
return order(QueryOrdering(orderings: orderings))
}

Expand Down
2 changes: 1 addition & 1 deletion GRDB/QueryInterface/Association/AssociationRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension AssociationRequest {
return AssociationRequest(query: query.filter(predicate))
}

func order(_ orderings: [SQLOrderingTerm]) -> AssociationRequest {
func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> AssociationRequest {
return AssociationRequest(query: query.order(orderings))
}

Expand Down
88 changes: 3 additions & 85 deletions GRDB/QueryInterface/QueryInterfaceQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ extension QueryInterfaceQuery {
return query
}

func order(_ orderings: [SQLOrderingTerm]) -> QueryInterfaceQuery {
func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> QueryInterfaceQuery {
return order(QueryOrdering(orderings: orderings))
}

Expand Down Expand Up @@ -248,7 +248,7 @@ extension QueryInterfaceQuery {
sql += " HAVING " + havingExpression.expressionSQL(&context)
}

let orderings = finalizedOrdering.resolve()
let orderings = try finalizedOrdering.resolve(db)
if !orderings.isEmpty {
sql += " ORDER BY " + orderings.map { $0.orderingTermSQL(&context) }.joined(separator: ", ")
}
Expand Down Expand Up @@ -300,7 +300,7 @@ extension QueryInterfaceQuery {
}

if let limit = limit {
let orderings = finalizedOrdering.resolve()
let orderings = try finalizedOrdering.resolve(db)
if !orderings.isEmpty {
sql += " ORDER BY " + orderings.map { $0.orderingTermSQL(&context) }.joined(separator: ", ")
}
Expand Down Expand Up @@ -519,88 +519,6 @@ struct AssociationJoin {
}
}

// MARK: - QueryOrdering

struct QueryOrdering {
private var elements: [Element] = []
var isReversed: Bool

private enum Element {
case orderingTerm(SQLOrderingTerm)
case queryOrdering(QueryOrdering)

var reversed: Element {
switch self {
case .orderingTerm(let orderingTerm):
return .orderingTerm(orderingTerm.reversed)
case .queryOrdering(let queryOrdering):
return .queryOrdering(queryOrdering.reversed)
}
}

func qualified(with alias: TableAlias) -> Element {
switch self {
case .orderingTerm(let orderingTerm):
return .orderingTerm(orderingTerm.qualifiedOrdering(with: alias))
case .queryOrdering(let queryOrdering):
return .queryOrdering(queryOrdering.qualified(with: alias))
}
}

func resolve() -> [SQLOrderingTerm] {
switch self {
case .orderingTerm(let orderingTerm):
return [orderingTerm]
case .queryOrdering(let queryOrdering):
return queryOrdering.resolve()
}
}
}

private init(elements: [Element], isReversed: Bool) {
self.elements = elements
self.isReversed = isReversed
}

init() {
self.init(
elements: [],
isReversed: false)
}

init(orderings: [SQLOrderingTerm]) {
self.init(
elements: orderings.map { .orderingTerm($0) },
isReversed: false)
}

var reversed: QueryOrdering {
return QueryOrdering(
elements: elements,
isReversed: !isReversed)
}

func qualified(with alias: TableAlias) -> QueryOrdering {
return QueryOrdering(
elements: elements.map { $0.qualified(with: alias) },
isReversed: isReversed)
}

func appending(_ ordering: QueryOrdering) -> QueryOrdering {
return QueryOrdering(
elements: elements + [.queryOrdering(ordering)],
isReversed: isReversed)
}

func resolve() -> [SQLOrderingTerm] {
if isReversed {
return elements.flatMap { $0.reversed.resolve() }
} else {
return elements.flatMap { $0.resolve() }
}
}
}

// MARK: - SQLSource

enum SQLSource {
Expand Down
25 changes: 20 additions & 5 deletions GRDB/QueryInterface/QueryInterfaceRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,20 @@ extension QueryInterfaceRequest : DerivableRequest, AggregatingRequest {
return QueryInterfaceRequest(query: query.having(predicate))
}

/// Creates a request with the provided *orderings*.
/// Creates a request with the provided *orderings promise*.
///
/// // SELECT * FROM player ORDER BY name
/// var request = Player.all()
/// request = request.order([Column("name")])
/// request = request.order { _ in [Column("name")] }
///
/// Any previous ordering is replaced:
///
/// // SELECT * FROM player ORDER BY name
/// request
/// .order([Column("email")])
/// .order{ _ in [Column("email")] }
/// .reversed()
/// .order([Column("name")])
public func order(_ orderings: [SQLOrderingTerm]) -> QueryInterfaceRequest<T> {
/// .order{ _ in [Column("name")] }
public func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> QueryInterfaceRequest<T> {
return QueryInterfaceRequest(query: query.order(orderings))
}

Expand Down Expand Up @@ -360,6 +360,21 @@ extension TableRecord {
return all().order(orderings)
}

/// Creates a request sorted by primary key.
///
/// // SELECT * FROM player ORDER BY id
/// let request = Player.orderByPrimaryKey()
///
/// // SELECT * FROM country ORDER BY code
/// let request = Country.orderByPrimaryKey()
///
/// The selection defaults to all columns. This default can be changed for
/// all requests by the `TableRecord.databaseSelection` property, or
/// for individual requests with the `TableRecord.select` method.
public static func orderByPrimaryKey() -> QueryInterfaceRequest<Self> {
return all().orderByPrimaryKey()
}

/// Creates a request sorted according to *sql*.
///
/// // SELECT * FROM player ORDER BY name
Expand Down
81 changes: 81 additions & 0 deletions GRDB/QueryInterface/QueryOrdering.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// QueryOrdering provides the order clause to QueryInterfaceQuery
/// and AssociationQuery.
struct QueryOrdering {
private var elements: [Element] = []
var isReversed: Bool

private enum Element {
case orderingTerms((Database) throws -> [SQLOrderingTerm])
case queryOrdering(QueryOrdering)

var reversed: Element {
switch self {
case .orderingTerms(let orderings):
return .orderingTerms { db in try orderings(db).map { $0.reversed } }
case .queryOrdering(let queryOrdering):
return .queryOrdering(queryOrdering.reversed)
}
}

func qualified(with alias: TableAlias) -> Element {
switch self {
case .orderingTerms(let orderings):
return .orderingTerms { db in try orderings(db).map { $0.qualifiedOrdering(with: alias) } }
case .queryOrdering(let queryOrdering):
return .queryOrdering(queryOrdering.qualified(with: alias))
}
}

func resolve(_ db: Database) throws -> [SQLOrderingTerm] {
switch self {
case .orderingTerms(let orderings):
return try orderings(db)
case .queryOrdering(let queryOrdering):
return try queryOrdering.resolve(db)
}
}
}

private init(elements: [Element], isReversed: Bool) {
self.elements = elements
self.isReversed = isReversed
}

init() {
self.init(
elements: [],
isReversed: false)
}

init(orderings: @escaping (Database) throws -> [SQLOrderingTerm]) {
self.init(
elements: [.orderingTerms(orderings)],
isReversed: false)
}

var reversed: QueryOrdering {
return QueryOrdering(
elements: elements,
isReversed: !isReversed)
}

func qualified(with alias: TableAlias) -> QueryOrdering {
return QueryOrdering(
elements: elements.map { $0.qualified(with: alias) },
isReversed: isReversed)
}

func appending(_ ordering: QueryOrdering) -> QueryOrdering {
return QueryOrdering(
elements: elements + [.queryOrdering(ordering)],
isReversed: isReversed)
}

func resolve(_ db: Database) throws -> [SQLOrderingTerm] {
if isReversed {
return try elements.flatMap { try $0.reversed.resolve(db) }
} else {
return try elements.flatMap { try $0.resolve(db) }
}
}
}
Loading

0 comments on commit ff69d3f

Please sign in to comment.