Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/Tests/GRDBTests/DatabaseCursorTests.swift
T

333 lines
13 KiB
Swift

import XCTest
import GRDB
class DatabaseCursorTests: GRDBTestCase {
// TODO: this test should be duplicated for all cursor types
func testNextReturnsNilAfterExhaustion() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1 WHERE 0")
XCTAssert(try cursor.next() == nil) // end
XCTAssert(try cursor.next() == nil) // past the end
}
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1")
XCTAssertEqual(try cursor.next()!, 1)
XCTAssert(try cursor.next() == nil) // end
XCTAssert(try cursor.next() == nil) // past the end
}
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1 UNION ALL SELECT 2")
XCTAssertEqual(try cursor.next()!, 1)
XCTAssertEqual(try cursor.next()!, 2)
XCTAssert(try cursor.next() == nil) // end
XCTAssert(try cursor.next() == nil) // past the end
}
}
}
// TODO: this test should be duplicated for all cursor types
func testNextFollowedByForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1")
XCTAssertEqual(try cursor.next()!, 1)
try cursor.forEach { _ in
XCTFail("Cursor iteration should have ended")
}
XCTAssert(try cursor.next() == nil) // end
}
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1 UNION SELECT 2")
XCTAssertEqual(try cursor.next()!, 1)
try cursor.forEach { i in
XCTAssertEqual(i, 2)
}
XCTAssert(try cursor.next() == nil) // end
}
do {
let cursor = try Int.fetchCursor(db, sql: "SELECT 1 UNION SELECT 2 UNION SELECT 3")
XCTAssertEqual(try cursor.next()!, 1)
try XCTAssertEqual(Array(cursor), [2, 3])
XCTAssert(try cursor.next() == nil) // end
}
do {
struct Break: Error { }
let cursor = try Int.fetchCursor(db, sql: "SELECT 1 UNION SELECT 2 UNION SELECT 3")
try? cursor.forEach { i in
XCTAssertEqual(i, 1)
throw Break()
}
try? cursor.forEach { i in
XCTAssertEqual(i, 2)
throw Break()
}
try? cursor.forEach { i in
XCTAssertEqual(i, 3)
throw Break()
}
XCTAssert(try cursor.next() == nil) // end
}
}
}
// TODO: this test should be duplicated for all cursor types
func testStepError() throws {
let dbQueue = try makeDatabaseQueue()
let customError = NSError(domain: "Custom", code: 0xDEAD)
try dbQueue.inDatabase { db in
db.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError })
let cursor = try Int.fetchCursor(db, sql: "SELECT throw()")
do {
_ = try cursor.next()
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message, "\(customError)")
XCTAssertEqual(error.sql!, "SELECT throw()")
XCTAssertEqual(error.description, "SQLite error 1: \(customError) - while executing `SELECT throw()`")
}
}
}
// TODO: this test should be duplicated for all cursor types
func testStepDatabaseError() throws {
let dbQueue = try makeDatabaseQueue()
let customError = DatabaseError(resultCode: ResultCode(rawValue: 0xDEAD), message: "custom error")
try dbQueue.inDatabase { db in
db.add(function: DatabaseFunction("throw", argumentCount: 0, pure: true) { _ in throw customError })
let cursor = try Int.fetchCursor(db, sql: "SELECT throw()")
do {
_ = try cursor.next()
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode.rawValue, 0xAD)
XCTAssertEqual(error.extendedResultCode.rawValue, 0xDEAD)
XCTAssertEqual(error.message, "custom error")
XCTAssertEqual(error.sql!, "SELECT throw()")
XCTAssertEqual(error.description, "SQLite error 173: custom error - while executing `SELECT throw()`")
}
}
}
// Regression test for http://github.com/groue/GRDB.swift/issues/583
func testIssue583() throws {
struct User: Codable, TableRecord, FetchableRecord, MutablePersistableRecord {
static let databaseTableName: String = "user"
var id: Int64?
var username: String
var isFlagged: Bool
init(id: Int64? = nil, username: String, isFlagged: Bool = false) {
self.id = id
self.username = username
self.isFlagged = isFlagged
}
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
struct FlagUser: Codable, TableRecord, FetchableRecord, MutablePersistableRecord {
static let databaseTableName: String = "flagUser"
var username: String
}
let queue = try makeDatabaseQueue()
try queue.write { database in
try database.create(table: User.databaseTableName) { definition in
definition.column("id", .integer).primaryKey(autoincrement: true)
definition.column("username", .text).notNull()
definition.column("isFlagged", .boolean).notNull().defaults(to: false)
}
try database.create(table: FlagUser.databaseTableName) { definition in
definition.column("username", .text).notNull()
}
try [Int](0...50).forEach {
var user = User(username: "User\($0)")
try user.insert(database)
}
try [Int](40...60).forEach {
var flag = FlagUser(username: "User\($0)")
try flag.insert(database)
}
}
let query = "SELECT * FROM flagUser WHERE (SELECT COUNT(id) FROM user WHERE username = flagUser.username AND isFlagged = 1) = 0"
try queue.inDatabase { database in
let cursor = try FlagUser.fetchCursor(database, sql: query)
while let flagged = try cursor.next() {
_ = try User.fetchOne(database, sql: "SELECT * FROM user WHERE username = '\(flagged.username)' LIMIT 1") ??
User(username: flagged.username)
}
}
// For the record, the lines below show how this test used to fail,
// with raw C SQLite3 apis. The faulty line is the call to
// sqlite3_set_authorizer during the statement iteration.
// if #available(OSX 10.14, *) {
// var connection: SQLiteConnection? = nil
// sqlite3_open_v2(":memory:", &connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX, nil)
// sqlite3_extended_result_codes(connection, 1)
//
// sqlite3_exec(connection, """
// CREATE TABLE user (username TEXT NOT NULL);
// CREATE TABLE flagUser (username TEXT NOT NULL);
// INSERT INTO flagUser (username) VALUES ('User1');
// INSERT INTO flagUser (username) VALUES ('User2');
// """, nil, nil, nil)
//
// var statement: SQLiteStatement? = nil
// sqlite3_set_authorizer(connection, { (_, _, _, _, _, _) in SQLITE_OK }, nil)
// sqlite3_prepare_v3(connection, """
// SELECT * FROM flagUser WHERE (SELECT COUNT(*) FROM user WHERE username = flagUser.username) = 0
// """, -1, 0, &statement, nil)
// sqlite3_set_authorizer(connection, nil, nil)
// while true {
// let code = sqlite3_step(statement)
// if code == SQLITE_DONE {
// break
// } else if code == SQLITE_ROW {
// // part of the compilation of another statement, here
// // reduced to the strict minimum that reproduces
// // the error.
// sqlite3_set_authorizer(connection, nil, nil)
// } else {
// print(String(cString: sqlite3_errmsg(connection)))
// XCTFail("Error \(code)")
// break
// }
// }
// sqlite3_finalize(statement)
// sqlite3_close_v2(connection)
// }
}
// For profiling tests
let profilingSQL = """
WITH RECURSIVE
cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<10000000)
SELECT x FROM cnt
"""
// Profiling test
func testRowCursorStep() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: RowCursor = try Row.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testRowCursorForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: RowCursor = try Row.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
// Profiling test
func testDatabaseValueCursorStep() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: DatabaseValueCursor<Int> = try Int.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testDatabaseValueCursorForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: DatabaseValueCursor<Int> = try Int.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
// Profiling test
func testNullableDatabaseValueCursorStep() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: DatabaseValueCursor<Int?> = try Optional<Int>.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testNullableDatabaseValueCursorForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: DatabaseValueCursor<Int?> = try Optional<Int>.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
// Profiling test
func testFastDatabaseValueCursorStep() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: FastDatabaseValueCursor<Int> = try Int.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testFastDatabaseValueCursorForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: FastDatabaseValueCursor<Int> = try Int.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
// Profiling test
func testFastNullableDatabaseValueCursorStep() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: FastDatabaseValueCursor<Int?> = try Optional<Int>.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testFastNullableDatabaseValueCursorForEach() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: FastDatabaseValueCursor<Int?> = try Optional<Int>.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
// Profiling test
func testRecordCursorStep() throws {
struct S: FetchableRecord { init(row: Row) { } }
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: RecordCursor<S> = try S.fetchCursor(db, sql: profilingSQL)
while let _ = try cursor.next() { }
}
}
// Profiling test
func testRecordCursorForEach() throws {
struct S: FetchableRecord { init(row: Row) { } }
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let cursor: RecordCursor<S> = try S.fetchCursor(db, sql: profilingSQL)
try cursor.forEach { _ in }
}
}
}