From fa31c484dae04da628acfe5e732f4b7556e7e6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 5 Jun 2018 22:46:02 +0200 Subject: [PATCH 1/4] OrderedRequest now waits for the database connection before ordering is computed --- GRDB.xcodeproj/project.pbxproj | 8 ++ .../Association/Association.swift | 10 +-- .../Association/AssociationQuery.swift | 2 +- .../Association/AssociationRequest.swift | 2 +- GRDB/QueryInterface/QueryInterfaceQuery.swift | 88 +------------------ .../QueryInterfaceRequest.swift | 25 ++++-- GRDB/QueryInterface/QueryOrdering.swift | 81 +++++++++++++++++ GRDB/QueryInterface/RequestProtocols.swift | 39 ++++++-- GRDBCipher.xcodeproj/project.pbxproj | 6 ++ GRDBCustom.xcodeproj/project.pbxproj | 6 ++ 10 files changed, 164 insertions(+), 103 deletions(-) create mode 100644 GRDB/QueryInterface/QueryOrdering.swift diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj index 760ef2cb5d..4ee666f455 100755 --- a/GRDB.xcodeproj/project.pbxproj +++ b/GRDB.xcodeproj/project.pbxproj @@ -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 */; }; @@ -892,6 +895,7 @@ 5653EC0B2098738B00F46237 /* SQLGenerationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLGenerationContext.swift; sourceTree = ""; }; 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 = ""; }; 5657AAB81D107001006283EF /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = ""; }; 5657AB0E1D10899D006283EF /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 5657AB2F1D108BA9006283EF /* FoundationDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationDataTests.swift; sourceTree = ""; }; @@ -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 */, @@ -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 */, @@ -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 */, @@ -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 */, diff --git a/GRDB/QueryInterface/Association/Association.swift b/GRDB/QueryInterface/Association/Association.swift index c578b78e13..212fe3452a 100644 --- a/GRDB/QueryInterface/Association/Association.swift +++ b/GRDB/QueryInterface/Association/Association.swift @@ -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) @@ -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: @@ -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) } } diff --git a/GRDB/QueryInterface/Association/AssociationQuery.swift b/GRDB/QueryInterface/Association/AssociationQuery.swift index a89a25361f..fb70e808cd 100644 --- a/GRDB/QueryInterface/Association/AssociationQuery.swift +++ b/GRDB/QueryInterface/Association/AssociationQuery.swift @@ -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)) } diff --git a/GRDB/QueryInterface/Association/AssociationRequest.swift b/GRDB/QueryInterface/Association/AssociationRequest.swift index 3f7909a5e3..1f52aa2bc1 100644 --- a/GRDB/QueryInterface/Association/AssociationRequest.swift +++ b/GRDB/QueryInterface/Association/AssociationRequest.swift @@ -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)) } diff --git a/GRDB/QueryInterface/QueryInterfaceQuery.swift b/GRDB/QueryInterface/QueryInterfaceQuery.swift index 2e3eba87a8..abb7908180 100644 --- a/GRDB/QueryInterface/QueryInterfaceQuery.swift +++ b/GRDB/QueryInterface/QueryInterfaceQuery.swift @@ -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)) } @@ -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: ", ") } @@ -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: ", ") } @@ -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 { diff --git a/GRDB/QueryInterface/QueryInterfaceRequest.swift b/GRDB/QueryInterface/QueryInterfaceRequest.swift index f6b62bcdac..bf08c51573 100644 --- a/GRDB/QueryInterface/QueryInterfaceRequest.swift +++ b/GRDB/QueryInterface/QueryInterfaceRequest.swift @@ -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 { + /// .order{ _ in [Column("name")] } + public func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> QueryInterfaceRequest { return QueryInterfaceRequest(query: query.order(orderings)) } @@ -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 { + return all().orderByPrimaryKey() + } + /// Creates a request sorted according to *sql*. /// /// // SELECT * FROM player ORDER BY name diff --git a/GRDB/QueryInterface/QueryOrdering.swift b/GRDB/QueryInterface/QueryOrdering.swift new file mode 100644 index 0000000000..9d8822f01a --- /dev/null +++ b/GRDB/QueryInterface/QueryOrdering.swift @@ -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) } + } + } +} diff --git a/GRDB/QueryInterface/RequestProtocols.swift b/GRDB/QueryInterface/RequestProtocols.swift index a479757740..9182a9eb7d 100644 --- a/GRDB/QueryInterface/RequestProtocols.swift +++ b/GRDB/QueryInterface/RequestProtocols.swift @@ -202,6 +202,16 @@ extension TableRequest where Self: FilteredRequest { } } +extension TableRequest where Self: OrderedRequest { + /// Creates a request ordered by primary key. + public func orderByPrimaryKey() -> Self { + let tableName = self.databaseTableName + return order { db in + try db.primaryKey(tableName).columns.map { Column($0) } + } + } +} + // MARK: - AggregatingRequest /// The protocol for all requests that can aggregate. @@ -246,20 +256,20 @@ extension AggregatingRequest { /// The protocol for all requests that be ordered. public protocol OrderedRequest { - /// 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")]) - func order(_ orderings: [SQLOrderingTerm]) -> Self + /// .order{ _ in [Column("name")] } + func order(_ orderings: @escaping (Database) throws -> [SQLOrderingTerm]) -> Self /// Creates a request that reverses applied orderings. /// @@ -290,7 +300,24 @@ extension OrderedRequest { /// .reversed() /// .order(Column("name")) public func order(_ orderings: SQLOrderingTerm...) -> Self { - return order(orderings) + return order { _ in orderings } + } + + /// Creates a request with the provided *orderings*. + /// + /// // SELECT * FROM player ORDER BY name + /// var request = Player.all() + /// request = request.order(Column("name")) + /// + /// Any previous ordering is replaced: + /// + /// // SELECT * FROM player ORDER BY name + /// request + /// .order(Column("email")) + /// .reversed() + /// .order(Column("name")) + public func order(_ orderings: [SQLOrderingTerm]) -> Self { + return order { _ in orderings } } /// Creates a request with the provided *sql* used for sorting. diff --git a/GRDBCipher.xcodeproj/project.pbxproj b/GRDBCipher.xcodeproj/project.pbxproj index 6286c1471f..ca3c507435 100755 --- a/GRDBCipher.xcodeproj/project.pbxproj +++ b/GRDBCipher.xcodeproj/project.pbxproj @@ -273,6 +273,8 @@ 5653EC18209873A400F46237 /* SQLExpression+QueryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EC15209873A400F46237 /* SQLExpression+QueryInterface.swift */; }; 5653EC1A209873E600F46237 /* SQLGenerationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EC19209873E600F46237 /* SQLGenerationContext.swift */; }; 5653EC1B209873E600F46237 /* SQLGenerationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EC19209873E600F46237 /* SQLGenerationContext.swift */; }; + 5656BF5E20C7248700F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF5C20C7248700F98521 /* QueryOrdering.swift */; }; + 5656BF5F20C7248700F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF5C20C7248700F98521 /* QueryOrdering.swift */; }; 5657AABA1D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; }; 5657AABD1D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; }; 5657AB101D10899D006283EF /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; }; @@ -988,6 +990,7 @@ 5653EBB320961FE800F46237 /* AssociationBelongsToRowScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationBelongsToRowScopeTests.swift; sourceTree = ""; }; 5653EC15209873A400F46237 /* SQLExpression+QueryInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SQLExpression+QueryInterface.swift"; sourceTree = ""; }; 5653EC19209873E600F46237 /* SQLGenerationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLGenerationContext.swift; sourceTree = ""; }; + 5656BF5C20C7248700F98521 /* QueryOrdering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryOrdering.swift; sourceTree = ""; }; 5657AAB81D107001006283EF /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = ""; }; 5657AB0E1D10899D006283EF /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 5657AB2F1D108BA9006283EF /* FoundationDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationDataTests.swift; sourceTree = ""; }; @@ -1444,6 +1447,7 @@ 5605F18C1C6B1A8700235C62 /* QueryInterfaceQuery.swift */, 56300B6F1C53F592005A543B /* QueryInterfaceRequest.swift */, 5653EBA120961FCA00F46237 /* QueryInterfaceRequest+Association.swift */, + 5656BF5C20C7248700F98521 /* QueryOrdering.swift */, 5616AAF8207CD5A900AC3664 /* RequestProtocols.swift */, 5605F1891C6B1A8700235C62 /* SQLCollatedExpression.swift */, 566475991D97D8A000FF74B8 /* SQLCollection.swift */, @@ -2185,6 +2189,7 @@ 56CEB55B1EAA359A00BFAF62 /* SQLOrdering.swift in Sources */, 56BF6D301DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 560FC52A1CB003810014AA8E /* DatabaseError.swift in Sources */, + 5656BF5E20C7248700F98521 /* QueryOrdering.swift in Sources */, 56D51D011EA789FA0074638A /* FetchableRecord+TableRecord.swift in Sources */, 566475BB1D981AD200FF74B8 /* SQLSpecificExpressible+QueryInterface.swift in Sources */, 560FC52B1CB003810014AA8E /* DatabaseValue.swift in Sources */, @@ -2619,6 +2624,7 @@ 56CEB55E1EAA359A00BFAF62 /* SQLOrdering.swift in Sources */, 56BF6D331DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, 56AFC9FF1CB1A8BB00F48B96 /* DatabaseValueConvertible.swift in Sources */, + 5656BF5F20C7248700F98521 /* QueryOrdering.swift in Sources */, 56D51D041EA789FA0074638A /* FetchableRecord+TableRecord.swift in Sources */, 566475BE1D981AD200FF74B8 /* SQLSpecificExpressible+QueryInterface.swift in Sources */, 56AFCA001CB1A8BB00F48B96 /* Record.swift in Sources */, diff --git a/GRDBCustom.xcodeproj/project.pbxproj b/GRDBCustom.xcodeproj/project.pbxproj index fcf06e10f0..1880e46011 100755 --- a/GRDBCustom.xcodeproj/project.pbxproj +++ b/GRDBCustom.xcodeproj/project.pbxproj @@ -99,6 +99,8 @@ 5653EB8320961FB200F46237 /* AssociationBelongsToRowScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EB6520961FB200F46237 /* AssociationBelongsToRowScopeTests.swift */; }; 5653EC082098737400F46237 /* SQLGenerationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EC072098737400F46237 /* SQLGenerationContext.swift */; }; 5653EC092098737400F46237 /* SQLGenerationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5653EC072098737400F46237 /* SQLGenerationContext.swift */; }; + 5656BF5A20C7247100F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF5820C7247100F98521 /* QueryOrdering.swift */; }; + 5656BF5B20C7247100F98521 /* QueryOrdering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5656BF5820C7247100F98521 /* QueryOrdering.swift */; }; 5657AABB1D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; }; 5657AABE1D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; }; 5657AB111D10899D006283EF /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; }; @@ -658,6 +660,7 @@ 5653EB6420961FB200F46237 /* AssociationBelongsToDecodableRecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationBelongsToDecodableRecordTests.swift; sourceTree = ""; }; 5653EB6520961FB200F46237 /* AssociationBelongsToRowScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationBelongsToRowScopeTests.swift; sourceTree = ""; }; 5653EC072098737400F46237 /* SQLGenerationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLGenerationContext.swift; sourceTree = ""; }; + 5656BF5820C7247100F98521 /* QueryOrdering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryOrdering.swift; sourceTree = ""; }; 5657AAB81D107001006283EF /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = ""; }; 5657AB0E1D10899D006283EF /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 5657AB2F1D108BA9006283EF /* FoundationDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationDataTests.swift; sourceTree = ""; }; @@ -1088,6 +1091,7 @@ 5605F18C1C6B1A8700235C62 /* QueryInterfaceQuery.swift */, 56300B6F1C53F592005A543B /* QueryInterfaceRequest.swift */, 5653EB5320961F7D00F46237 /* QueryInterfaceRequest+Association.swift */, + 5656BF5820C7247100F98521 /* QueryOrdering.swift */, 5616AAF4207CD59300AC3664 /* RequestProtocols.swift */, 5605F1891C6B1A8700235C62 /* SQLCollatedExpression.swift */, 566475991D97D8A000FF74B8 /* SQLCollection.swift */, @@ -1858,6 +1862,7 @@ F3BA80201CFB288C003DC1BA /* Date.swift in Sources */, 56A8C2351D1914540096E9D4 /* UUID.swift in Sources */, 56BF6D341DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, + 5656BF5B20C7247100F98521 /* QueryOrdering.swift in Sources */, 56D51D051EA789FA0074638A /* FetchableRecord+TableRecord.swift in Sources */, F3BA80151CFB2876003DC1BA /* DatabaseWriter.swift in Sources */, F3BA800A1CFB286A003DC1BA /* Configuration.swift in Sources */, @@ -2130,6 +2135,7 @@ F3BA807C1CFB2E61003DC1BA /* Date.swift in Sources */, 56A8C2321D1914540096E9D4 /* UUID.swift in Sources */, 56BF6D311DEF47DA006039A3 /* Fixits-0-84-0.swift in Sources */, + 5656BF5A20C7247100F98521 /* QueryOrdering.swift in Sources */, 56D51D021EA789FA0074638A /* FetchableRecord+TableRecord.swift in Sources */, F3BA80711CFB2E55003DC1BA /* DatabaseWriter.swift in Sources */, F3BA80661CFB2E55003DC1BA /* Configuration.swift in Sources */, From 7e6de4fbd06c0ce5e139eb0f76837c49ff740837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 5 Jun 2018 22:46:24 +0200 Subject: [PATCH 2/4] Test TableRequest.orderByPrimaryKey() --- .../RecordMinimalPrimaryKeyRowIDTests.swift | 14 +++++++++++++- .../RecordMinimalPrimaryKeySingleTests.swift | 16 ++++++++++++++-- .../RecordPrimaryKeyHiddenRowIDTests.swift | 16 ++++++++++++++-- .../RecordPrimaryKeyMultipleTests.swift | 16 ++++++++++++++-- Tests/GRDBTests/RecordPrimaryKeyNoneTests.swift | 16 ++++++++++++++-- Tests/GRDBTests/RecordPrimaryKeyRowIDTests.swift | 16 ++++++++++++++-- .../GRDBTests/RecordPrimaryKeySingleTests.swift | 16 ++++++++++++++-- ...ingleWithReplaceConflictResolutionTests.swift | 16 ++++++++++++++-- 8 files changed, 111 insertions(+), 15 deletions(-) diff --git a/Tests/GRDBTests/RecordMinimalPrimaryKeyRowIDTests.swift b/Tests/GRDBTests/RecordMinimalPrimaryKeyRowIDTests.swift index 9c4e9eb644..65dd3e4ca1 100644 --- a/Tests/GRDBTests/RecordMinimalPrimaryKeyRowIDTests.swift +++ b/Tests/GRDBTests/RecordMinimalPrimaryKeyRowIDTests.swift @@ -379,7 +379,19 @@ class RecordMinimalPrimaryKeyRowIDTests : GRDBTestCase { XCTAssertTrue(fetchedRecord.id == record.id) } } - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = MinimalRowID.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"minimalRowIDs\" ORDER BY \"id\"") + } + } + // MARK: - Fetch With Primary Key diff --git a/Tests/GRDBTests/RecordMinimalPrimaryKeySingleTests.swift b/Tests/GRDBTests/RecordMinimalPrimaryKeySingleTests.swift index 53de5100eb..1615624cd8 100644 --- a/Tests/GRDBTests/RecordMinimalPrimaryKeySingleTests.swift +++ b/Tests/GRDBTests/RecordMinimalPrimaryKeySingleTests.swift @@ -395,8 +395,20 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase { XCTAssertTrue(fetchedRecord.UUID == record.UUID) } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = MinimalSingle.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"minimalSingles\" ORDER BY \"UUID\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeyHiddenRowIDTests.swift b/Tests/GRDBTests/RecordPrimaryKeyHiddenRowIDTests.swift index 1b7f0d60e2..4a8ab4b905 100644 --- a/Tests/GRDBTests/RecordPrimaryKeyHiddenRowIDTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeyHiddenRowIDTests.swift @@ -462,8 +462,20 @@ class RecordPrimaryKeyHiddenRowIDTests : GRDBTestCase { XCTAssertTrue(abs(fetchedRecord.creationDate.timeIntervalSince(record.creationDate)) < 1e-3) // ISO-8601 is precise to the millisecond. } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Person.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT *, \"rowid\" FROM \"persons\" ORDER BY \"rowid\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeyMultipleTests.swift b/Tests/GRDBTests/RecordPrimaryKeyMultipleTests.swift index 486610a9e6..c153f6ffc5 100644 --- a/Tests/GRDBTests/RecordPrimaryKeyMultipleTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeyMultipleTests.swift @@ -415,8 +415,20 @@ class RecordPrimaryKeyMultipleTests: GRDBTestCase { XCTAssertTrue(fetchedRecord.native == record.native) } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Citizenship.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"citizenships\" ORDER BY \"personName\", \"countryName\"") + } + } + + // MARK: - Exists func testExistsWithNilPrimaryKeyReturnsFalse() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeyNoneTests.swift b/Tests/GRDBTests/RecordPrimaryKeyNoneTests.swift index 76890fa946..34f15af93b 100644 --- a/Tests/GRDBTests/RecordPrimaryKeyNoneTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeyNoneTests.swift @@ -116,8 +116,20 @@ class RecordPrimaryKeyNoneTests: GRDBTestCase { XCTAssertTrue(fetchedRecord.email == record.email) } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Item.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"items\" ORDER BY \"rowid\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeyRowIDTests.swift b/Tests/GRDBTests/RecordPrimaryKeyRowIDTests.swift index 8ca0f792e7..df044d945d 100644 --- a/Tests/GRDBTests/RecordPrimaryKeyRowIDTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeyRowIDTests.swift @@ -458,8 +458,20 @@ class RecordPrimaryKeyRowIDTests: GRDBTestCase { XCTAssertTrue(abs(fetchedRecord.creationDate.timeIntervalSince(record.creationDate)) < 1e-3) // ISO-8601 is precise to the millisecond. } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Person.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"persons\" ORDER BY \"id\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeySingleTests.swift b/Tests/GRDBTests/RecordPrimaryKeySingleTests.swift index 37e7f840a9..fdd2ae6921 100644 --- a/Tests/GRDBTests/RecordPrimaryKeySingleTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeySingleTests.swift @@ -406,8 +406,20 @@ class RecordPrimaryKeySingleTests: GRDBTestCase { XCTAssertTrue(fetchedRecord.name == record.name) } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Pet.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"pets\" ORDER BY \"UUID\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { diff --git a/Tests/GRDBTests/RecordPrimaryKeySingleWithReplaceConflictResolutionTests.swift b/Tests/GRDBTests/RecordPrimaryKeySingleWithReplaceConflictResolutionTests.swift index 39c99e05c5..3a550c14cf 100644 --- a/Tests/GRDBTests/RecordPrimaryKeySingleWithReplaceConflictResolutionTests.swift +++ b/Tests/GRDBTests/RecordPrimaryKeySingleWithReplaceConflictResolutionTests.swift @@ -401,8 +401,20 @@ class RecordPrimaryKeySingleWithReplaceConflictResolutionTests: GRDBTestCase { XCTAssertTrue(fetchedRecord.email == record.email) } } - - + + + // MARK: - Order By Primary Key + + func testOrderByPrimaryKey() throws { + let dbQueue = try makeDatabaseQueue() + try dbQueue.inDatabase { db in + let request = Email.orderByPrimaryKey() + let sqlRequest = try SQLRequest(db, request: request) + XCTAssertEqual(sqlRequest.sql, "SELECT * FROM \"emails\" ORDER BY \"email\"") + } + } + + // MARK: - Fetch With Primary Key func testFetchCursorWithPrimaryKeys() throws { From 0b1bc80c4175e98fc8444ffd8deaa12c4659a265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 5 Jun 2018 22:53:05 +0200 Subject: [PATCH 3/4] Document orderByPrimaryKey --- README.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 19f01fe5a4..51911f83a6 100644 --- a/README.md +++ b/README.md @@ -2360,8 +2360,8 @@ try Document.fetchOne(db, key: 1) // Document? For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: ```swift -// SELECT * FROM citizenship WHERE playerID = 1 AND countryISOCode = 'FR' -try Citizenship.fetchOne(db, key: ["playerID": 1, "countryISOCode": "FR"]) // Citizenship? +// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' +try Citizenship.fetchOne(db, key: ["citizenId": 1, "countryCode": "FR"]) // Citizenship? ``` @@ -3547,8 +3547,8 @@ You can now build requests with the following methods: `all`, `none`, `select`, // SELECT * FROM country WHERE isoCode IN ('FR', 'US') Country.filter(keys: ["FR", "US"]) - // SELECT * FROM citizenship WHERE playerID = 1 AND countryISOCode = 'FR' - Citizenship.filter(key: ["playerID": 1, "countryISOCode": "FR"]) + // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' + Citizenship.filter(key: ["citizenId": 1, "countryCode": "FR"]) // SELECT * FROM player WHERE email = 'arthur@example.com' Player.filter(key: ["email": "arthur@example.com"]) @@ -3600,6 +3600,19 @@ You can now build requests with the following methods: `all`, `none`, `select`, Player.order(scoreColumn).order(nameColumn) ``` + `orderByPrimaryKey()` sorts by primary key: + + ```swift + // SELECT * FROM player ORDER BY id + Player.orderByPrimaryKey() + + // SELECT * FROM country ORDER BY code + Country.orderByPrimaryKey() + + // SELECT * FROM citizenship ORDER BY citizenId, countryCode + Citizenship.orderByPrimaryKey() + ``` + - `reversed()` reverses the eventual orderings. ```swift @@ -3970,8 +3983,8 @@ try Document.fetchOne(db, key: 1) // Document? For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: ```swift -// SELECT * FROM citizenship WHERE playerID = 1 AND countryISOCode = 'FR' -try Citizenship.fetchOne(db, key: ["playerID": 1, "countryISOCode": "FR"]) // Citizenship? +// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' +try Citizenship.fetchOne(db, key: ["citizenId": 1, "countryCode": "FR"]) // Citizenship? // SELECT * FROM player WHERE email = 'arthur@example.com' try Player.fetchOne(db, key: ["email": "arthur@example.com"]) // Player? @@ -3996,8 +4009,8 @@ let country = try request.fetchOne(db) // Country? let request = Country.filter(keys: ["FR", "US"]) let countries = try request.fetchAll(db) // [Country] -// SELECT * FROM citizenship WHERE playerID = 1 AND countryISOCode = 'FR' -let request = Citizenship.filter(key: ["playerID": 1, "countryISOCode": "FR"]) +// SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' +let request = Citizenship.filter(key: ["citizenId": 1, "countryCode": "FR"]) let citizenship = request.fetchOne(db) // Citizenship? // SELECT * FROM player WHERE email = 'arthur@example.com' @@ -4086,8 +4099,8 @@ try Document.deleteOne(db, key: 1) For multiple-column primary keys and unique keys defined by unique indexes, provide a dictionary: ```swift -// DELETE FROM citizenship WHERE playerID = 1 AND countryISOCode = 'FR' -try Citizenship.deleteOne(db, key: ["playerID": 1, "countryISOCode": "FR"]) +// DELETE FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' +try Citizenship.deleteOne(db, key: ["citizenId": 1, "countryCode": "FR"]) // DELETE FROM player WHERE email = 'arthur@example.com' Player.deleteOne(db, key: ["email": "arthur@example.com"]) @@ -5775,7 +5788,7 @@ let controller = FetchedRecordsController( // Using SQL, and eventual arguments: let controller = FetchedRecordsController( dbQueue, - sql: "SELECT * FROM player ORDER BY name WHERE countryIsoCode = ?", + sql: "SELECT * FROM player ORDER BY name WHERE countryCode = ?", arguments: ["FR"]) ``` From 018fac8c57cb79a884c9e6084689e43a49a0a5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Tue, 5 Jun 2018 23:07:00 +0200 Subject: [PATCH 4/4] CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b73c0ae7d0..d40b830dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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