Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delayed Request Ordering #365

Merged
merged 4 commits into from
Jun 6, 2018
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
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