Skip to content

Commit eff0651

Browse files
committed
Make asc/desc idempotent, query reversible
This commit adds behavior that internally tracks ascending/descending state on expressions, expanding only when necessary. This makes it possible to reverse queries by reversing each of the stored sort descriptors. let sorted = users.order(name) // SELECT * FROM "users" ORDER BY "name" let reversed = sorted.reverse() // SELECT * FROM "users" ORDER BY "name" DESC let rereversed = reversed.reverse() // SELECT * FROM "users" ORDER BY "name" ASC This also allows for more array-like access, including `last`: users.last // SELECT * FROM "users" ORDER BY "ROWID" DESC LIMIT 1 Signed-off-by: Stephen Celis <[email protected]>
1 parent 12cdc95 commit eff0651

File tree

5 files changed

+87
-25
lines changed

5 files changed

+87
-25
lines changed

SQLite Tests/QueryTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,27 @@ class QueryTests: XCTestCase {
198198
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
199199
}
200200

201+
func test_order_whenChained_overridesOrder() {
202+
let query = users.order(email).order(age)
203+
204+
let SQL = "SELECT * FROM \"users\" ORDER BY \"age\""
205+
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
206+
}
207+
208+
func test_reverse_withoutOrder_ordersByRowIdDescending() {
209+
let query = users.reverse()
210+
211+
let SQL = "SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC"
212+
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
213+
}
214+
215+
func test_reverse_withOrder_reversesOrder() {
216+
let query = users.order(age, email.desc).reverse()
217+
218+
let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC"
219+
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
220+
}
221+
201222
func test_limit_compilesLimitClause() {
202223
let query = users.limit(5)
203224

SQLite Tests/SchemaTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ class SchemaTests: XCTestCase {
350350
func test_index_withMultipleColumns_executesCompoundIndexStatement() {
351351
CreateUsersTable(db)
352352
ExpectExecution(db,
353-
"CREATE INDEX \"index_users_on_age_DESC_email\" ON \"users\" (\"age\" DESC, \"email\")",
353+
"CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")",
354354
db.create(index: users, on: age.desc, email)
355355
)
356356
}

SQLite/Expression.swift

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public struct Expression<T> {
2929

3030
public let bindings: [Binding?]
3131

32+
internal var ascending: Bool?
33+
3234
/// Builds a SQL expression with a literal string and an optional list of
3335
/// bindings.
3436
///
@@ -61,17 +63,17 @@ public struct Expression<T> {
6163
}
6264

6365
/// Returns an ascending sort version of the expression.
64-
public var asc: Expression<()> {
65-
return Expression.join(" ", [self, Expression(literal: "ASC")])
66+
public var asc: Expression {
67+
var expression = self
68+
expression.ascending = true
69+
return expression
6670
}
6771

6872
/// Returns an descending sort version of the expression.
69-
public var desc: Expression<()> {
70-
return Expression.join(" ", [self, Expression(literal: "DESC")])
71-
}
72-
73-
internal init<V>(_ expression: Expression<V>) {
74-
self.init(literal: expression.SQL, expression.bindings)
73+
public var desc: Expression {
74+
var expression = self
75+
expression.ascending = false
76+
return expression
7577
}
7678

7779
internal static func join(separator: String, _ expressions: [Expressible]) -> Expression<()> {
@@ -84,6 +86,24 @@ public struct Expression<T> {
8486
return Expression<()>(literal: Swift.join(separator, SQL), bindings)
8587
}
8688

89+
internal init<V>(_ expression: Expression<V>) {
90+
self.init(literal: expression.SQL, expression.bindings)
91+
ascending = expression.ascending
92+
}
93+
94+
internal var ordered: Expression<()> {
95+
if let ascending = ascending {
96+
return Expression.join(" ", [self, Expression(literal: ascending ? "ASC" : "DESC")])
97+
}
98+
return Expression<()>(self)
99+
}
100+
101+
internal func reverse() -> Expression {
102+
var expression = self
103+
expression.ascending = expression.ascending.map(!) ?? false
104+
return expression
105+
}
106+
87107
// naïve compiler for statements that can't be bound, e.g., CREATE TABLE
88108
internal func compile() -> String {
89109
var idx = 0
@@ -882,6 +902,8 @@ public postfix func -- (column: Expression<Int?>) -> Setter {
882902

883903
// MARK: - Internal
884904

905+
internal let rowid = Expression<Int>("ROWID")
906+
885907
internal func transcode(literal: Binding?) -> String {
886908
if let literal = literal {
887909
if let literal = literal as? Blob { return literal.description }

SQLite/Query.swift

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,15 @@ public struct Query {
239239
///
240240
/// :returns: A query with the given ORDER BY clause applied.
241241
public func order(by: Expressible...) -> Query {
242+
return order(by)
243+
}
244+
245+
/// Sets an ORDER BY clause on the query.
246+
///
247+
/// :param: by An ordered list of columns and directions to sort by.
248+
///
249+
/// :returns: A query with the given ORDER BY clause applied.
250+
public func order(by: [Expressible]) -> Query {
242251
var query = self
243252
query.order = by
244253
return query
@@ -398,7 +407,7 @@ public struct Query {
398407

399408
private var orderClause: Expressible? {
400409
if order.count == 0 { return nil }
401-
let clause = Expression<()>.join(", ", order)
410+
let clause = Expression<()>.join(", ", order.map { $0.expression.ordered })
402411
return Expression<()>(literal: "ORDER BY \(clause.SQL)", clause.bindings)
403412
}
404413

@@ -413,17 +422,6 @@ public struct Query {
413422
return nil
414423
}
415424

416-
// MARK: - Array
417-
418-
/// The first result (or nil if the query has no results).
419-
public var first: Row? {
420-
var generator = limit(1).generate()
421-
return generator.next()
422-
}
423-
424-
/// Returns true if the query has no results.
425-
public var isEmpty: Bool { return first == nil }
426-
427425
// MARK: - Modifying
428426

429427
/// Runs an INSERT statement against the query.
@@ -585,9 +583,6 @@ public struct Query {
585583

586584
// MARK: - Aggregate Functions
587585

588-
/// Runs count(*) against the query and returns it.
589-
public var count: Int { return calculate(SQLite_count(*))! }
590-
591586
/// Runs count() against the query.
592587
///
593588
/// :param: column The column used for the calculation.
@@ -715,6 +710,30 @@ public struct Query {
715710
return select(expression).selectStatement.scalar() as? U
716711
}
717712

713+
// MARK: - Array
714+
715+
/// Runs count(*) against the query and returns it.
716+
public var count: Int { return calculate(SQLite_count(*))! }
717+
718+
/// Returns true if the query has no rows.
719+
public var isEmpty: Bool { return first == nil }
720+
721+
/// The first row (or nil if the query returns no rows).
722+
public var first: Row? {
723+
var generator = limit(1).generate()
724+
return generator.next()
725+
}
726+
727+
/// The last row (or nil if the query returns no rows).
728+
public var last: Row? {
729+
return reverse().first
730+
}
731+
732+
/// A query in reverse order.
733+
public func reverse() -> Query {
734+
return order(order.isEmpty ? [rowid.desc] : order.map { $0.expression.reverse() })
735+
}
736+
718737
}
719738

720739
/// A row object. Returned by iterating over a Query. Provides typed subscript

SQLite/Schema.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public extension Database {
117117
on columns: Expressible...
118118
) -> Statement {
119119
let create = createSQL("INDEX", false, unique, ifNotExists, indexName(table, on: columns))
120-
let joined = Expression<()>.join(", ", columns)
120+
let joined = Expression<()>.join(", ", columns.map { $0.expression.ordered })
121121
return run("\(create) ON \(quote(identifier: table.tableName)) (\(joined.compile()))")
122122
}
123123

0 commit comments

Comments
 (0)