Skip to content

Commit 8583eb6

Browse files
committed
Closes #285
1 parent 2c040ba commit 8583eb6

File tree

3 files changed

+230
-203
lines changed

3 files changed

+230
-203
lines changed

SQLite.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
5353
19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; };
5454
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
55+
19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; };
5556
19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
5657
19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
5758
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
@@ -60,6 +61,7 @@
6061
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
6162
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
6263
19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
64+
19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; };
6365
19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
6466
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
6567
19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
@@ -71,6 +73,7 @@
7173
19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
7274
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
7375
19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
76+
19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; };
7477
19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; };
7578
19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
7679
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
@@ -231,6 +234,7 @@
231234
19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = "<group>"; };
232235
19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = "<group>"; };
233236
19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = "<group>"; };
237+
19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = "<group>"; };
234238
19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = "<group>"; };
235239
19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = "<group>"; };
236240
3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = "<group>"; };
@@ -430,6 +434,7 @@
430434
19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */,
431435
19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */,
432436
D4DB368A20C09C9B00D5A58E /* SelectTests.swift */,
437+
19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */,
433438
);
434439
name = SQLiteTests;
435440
path = Tests/SQLiteTests;
@@ -861,6 +866,7 @@
861866
19A1785195182AF8731A8BDA /* RowTests.swift in Sources */,
862867
19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */,
863868
D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */,
869+
19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */,
864870
);
865871
runOnlyForDeploymentPostprocessing = 0;
866872
};
@@ -951,6 +957,7 @@
951957
19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */,
952958
19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */,
953959
D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */,
960+
19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */,
954961
);
955962
runOnlyForDeploymentPostprocessing = 0;
956963
};
@@ -1011,6 +1018,7 @@
10111018
19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */,
10121019
19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */,
10131020
D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */,
1021+
19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */,
10141022
);
10151023
runOnlyForDeploymentPostprocessing = 0;
10161024
};
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import XCTest
2+
#if SQLITE_SWIFT_STANDALONE
3+
import sqlite3
4+
#elseif SQLITE_SWIFT_SQLCIPHER
5+
import SQLCipher
6+
#elseif os(Linux)
7+
import CSQLite
8+
#else
9+
import SQLite3
10+
#endif
11+
@testable import SQLite
12+
13+
class QueryIntegrationTests: SQLiteTestCase {
14+
15+
let id = Expression<Int64>("id")
16+
let email = Expression<String>("email")
17+
let age = Expression<Int>("age")
18+
19+
override func setUp() {
20+
super.setUp()
21+
22+
createUsersTable()
23+
}
24+
25+
// MARK: -
26+
27+
func test_select() {
28+
let managerId = Expression<Int64>("manager_id")
29+
let managers = users.alias("managers")
30+
31+
let alice = try! db.run(users.insert(email <- "alice@example.com"))
32+
_ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice))
33+
34+
for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) {
35+
_ = user[users[managerId]]
36+
}
37+
}
38+
39+
func test_prepareRowIterator() {
40+
let names = ["a", "b", "c"]
41+
try! insertUsers(names)
42+
43+
let emailColumn = Expression<String>("email")
44+
let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] }
45+
46+
XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted())
47+
}
48+
49+
func test_ambiguousMap() {
50+
let names = ["a", "b", "c"]
51+
try! insertUsers(names)
52+
53+
let emails = try! db.prepare("select email from users", []).map { $0[0] as! String }
54+
55+
XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted())
56+
}
57+
58+
func test_select_optional() {
59+
let managerId = Expression<Int64?>("manager_id")
60+
let managers = users.alias("managers")
61+
62+
let alice = try! db.run(users.insert(email <- "alice@example.com"))
63+
_ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice))
64+
65+
for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) {
66+
_ = user[users[managerId]]
67+
}
68+
}
69+
70+
func test_select_codable() throws {
71+
let table = Table("codable")
72+
try db.run(table.create { builder in
73+
builder.column(Expression<Int>("int"))
74+
builder.column(Expression<String>("string"))
75+
builder.column(Expression<Bool>("bool"))
76+
builder.column(Expression<Double>("float"))
77+
builder.column(Expression<Double>("double"))
78+
builder.column(Expression<Date>("date"))
79+
builder.column(Expression<String?>("optional"))
80+
builder.column(Expression<Data>("sub"))
81+
})
82+
83+
let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4,
84+
date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil)
85+
let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8,
86+
date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1)
87+
88+
try db.run(table.insert(value))
89+
90+
let rows = try db.prepare(table)
91+
let values: [TestCodable] = try rows.map({ try $0.decode() })
92+
XCTAssertEqual(values.count, 1)
93+
XCTAssertEqual(values[0].int, 5)
94+
XCTAssertEqual(values[0].string, "6")
95+
XCTAssertEqual(values[0].bool, true)
96+
XCTAssertEqual(values[0].float, 7)
97+
XCTAssertEqual(values[0].double, 8)
98+
XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000))
99+
XCTAssertEqual(values[0].optional, "optional")
100+
XCTAssertEqual(values[0].sub?.int, 1)
101+
XCTAssertEqual(values[0].sub?.string, "2")
102+
XCTAssertEqual(values[0].sub?.bool, true)
103+
XCTAssertEqual(values[0].sub?.float, 3)
104+
XCTAssertEqual(values[0].sub?.double, 4)
105+
XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0))
106+
XCTAssertNil(values[0].sub?.optional)
107+
XCTAssertNil(values[0].sub?.sub)
108+
}
109+
110+
func test_scalar() {
111+
XCTAssertEqual(0, try! db.scalar(users.count))
112+
XCTAssertEqual(false, try! db.scalar(users.exists))
113+
114+
try! insertUsers("alice")
115+
XCTAssertEqual(1, try! db.scalar(users.select(id.average)))
116+
}
117+
118+
func test_pluck() {
119+
let rowid = try! db.run(users.insert(email <- "alice@example.com"))
120+
XCTAssertEqual(rowid, try! db.pluck(users)![id])
121+
}
122+
123+
func test_insert() {
124+
let id = try! db.run(users.insert(email <- "alice@example.com"))
125+
XCTAssertEqual(1, id)
126+
}
127+
128+
func test_insert_many() {
129+
let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]]))
130+
XCTAssertEqual(2, id)
131+
}
132+
133+
func test_upsert() throws {
134+
guard db.satisfiesMinimumVersion(minor: 24) else { return }
135+
let fetchAge = { () throws -> Int? in
136+
try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] }
137+
}
138+
139+
let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email))
140+
XCTAssertEqual(1, id)
141+
XCTAssertEqual(30, try fetchAge())
142+
143+
let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email))
144+
XCTAssertEqual(1, nextId)
145+
XCTAssertEqual(42, try fetchAge())
146+
}
147+
148+
func test_update() {
149+
let changes = try! db.run(users.update(email <- "alice@example.com"))
150+
XCTAssertEqual(0, changes)
151+
}
152+
153+
func test_delete() {
154+
let changes = try! db.run(users.delete())
155+
XCTAssertEqual(0, changes)
156+
}
157+
158+
func test_union() throws {
159+
let expectedIDs = [
160+
try db.run(users.insert(email <- "alice@example.com")),
161+
try db.run(users.insert(email <- "sally@example.com"))
162+
]
163+
164+
let query1 = users.filter(email == "alice@example.com")
165+
let query2 = users.filter(email == "sally@example.com")
166+
167+
let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] }
168+
XCTAssertEqual(expectedIDs, actualIDs)
169+
170+
let query3 = users.select(users[*], Expression<Int>(literal: "1 AS weight")).filter(email == "sally@example.com")
171+
let query4 = users.select(users[*], Expression<Int>(literal: "2 AS weight")).filter(email == "alice@example.com")
172+
173+
print(query3.union(query4).order(Expression<Int>(literal: "weight")).asSQL())
174+
175+
let orderedIDs = try db.prepare(query3.union(query4).order(Expression<Int>(literal: "weight"), email)).map { $0[id] }
176+
XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs)
177+
}
178+
179+
func test_no_such_column() throws {
180+
let doesNotExist = Expression<String>("doesNotExist")
181+
try! insertUser("alice")
182+
let row = try! db.pluck(users.filter(email == "alice@example.com"))!
183+
184+
XCTAssertThrowsError(try row.get(doesNotExist)) { error in
185+
if case QueryError.noSuchColumn(let name, _) = error {
186+
XCTAssertEqual("\"doesNotExist\"", name)
187+
} else {
188+
XCTFail("unexpected error: \(error)")
189+
}
190+
}
191+
}
192+
193+
func test_catchConstraintError() {
194+
try! db.run(users.insert(email <- "alice@example.com"))
195+
do {
196+
try db.run(users.insert(email <- "alice@example.com"))
197+
XCTFail("expected error")
198+
} catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT {
199+
// expected
200+
} catch let error {
201+
XCTFail("unexpected error: \(error)")
202+
}
203+
}
204+
205+
// https://github.com/stephencelis/SQLite.swift/issues/285
206+
func test_order_by_random() throws {
207+
try insertUsers(["a", "b", "c'"])
208+
let result = Array(try db.prepare(users.select(email).order(Expression<Int>.random()).limit(1)))
209+
XCTAssertEqual(1, result.count)
210+
}
211+
}
212+
213+
214+
private extension Connection {
215+
func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool {
216+
guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false }
217+
let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) }
218+
guard components.count == 3 else { return false }
219+
220+
return components[1] >= minor && components[2] >= patch
221+
}
222+
}

0 commit comments

Comments
 (0)