Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/Tests/GRDBTests/DatabasePoolSchemaCacheTests.swift
T
2025-01-28 12:28:03 +08:00

226 lines
9.3 KiB
Swift

import XCTest
@testable import GRDB
class DatabasePoolSchemaCacheTests : GRDBTestCase {
func testCache() throws {
let dbPool = try makeDatabasePool()
try dbPool.write { db in
try db.execute(sql: "CREATE TABLE items (id INTEGER PRIMARY KEY, email TEXT UNIQUE, foo INT, bar DOUBLE)")
try db.execute(sql: "CREATE INDEX foobar ON items(foo, bar)")
}
try dbPool.write { db in
// Assert that the writer cache is empty
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") == nil)
}
try dbPool.read { db in
// Assert that a reader cache is empty
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") == nil)
}
try dbPool.read { db in
// Warm cache in a reader
_ = try db.primaryKey("items")
_ = try db.columns(in: "items")
_ = try db.indexes(on: "items")
// Assert that reader cache is warm
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") != nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") != nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") != nil)
}
try dbPool.read { db in
// Assert that reader cache is still warm
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") != nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") != nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") != nil)
}
try dbPool.write { db in
// Warm cache in the writer
_ = try db.primaryKey("items")
_ = try db.columns(in: "items")
_ = try db.indexes(on: "items")
// Assert that writer cache is warm
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") != nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") != nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") != nil)
}
try dbPool.write { db in
// Assert that writer cache is still warm
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") != nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") != nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") != nil)
}
try dbPool.write { db in
// Change schema
try db.execute(sql: "DROP TABLE items")
// Assert that the writer cache is cleared
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") == nil)
}
try dbPool.read { db in
// Assert that reader cache is cleared
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") == nil)
}
try dbPool.read { db in
do {
// Assert that cache is used: we expect an error now that
// the cache is empty.
_ = try db.primaryKey("items")
XCTFail()
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message!, "no such table: items")
XCTAssertEqual(error.description, "SQLite error 1: no such table: items")
}
}
}
// TODO: fix flaky test
// func testCachedStatementsAreNotShared() throws {
// // This is a regression test.
// //
// // If cached statements were shared between reader connections, this
// // test would crash with fatal error: Database was not used on the
// // correct thread.
// let dbPool = try makeDatabasePool()
// try dbPool.write { db in
// try db.execute(sql: "CREATE TABLE items (id INTEGER PRIMARY KEY)")
// try db.execute(sql: "INSERT INTO items (id) VALUES (1)")
// }
//
// // Block 1 Block 2
// // SELECT 1 FROM items WHERE id = 1
// // >
// let s1 = DispatchSemaphore(value: 0)
// // SELECT 1 FROM items WHERE id = 1
//
// let block1 = { () in
// try! dbPool.read { db in
// let stmt = try! db.cachedStatement(sql: "SELECT * FROM items")
// XCTAssertEqual(try Int.fetchOne(stmt)!, 1)
// s1.signal()
// }
// }
// let block2 = { () in
// try! dbPool.read { db in
// _ = s1.wait(timeout: .distantFuture)
// let stmt = try! db.cachedStatement(sql: "SELECT * FROM items")
// XCTAssertEqual(try Int.fetchOne(stmt)!, 1)
// }
// }
// let blocks = [block1, block2]
// DispatchQueue.concurrentPerform(iterations: blocks.count) { index in
// blocks[index]()
// }
// }
func testCacheSnapshotIsolation() throws {
// This test checks that the schema cache follows snapshot isolation.
// and that writer and readers do not naively share the same cache.
let dbPool = try makeDatabasePool()
// writer reader
// CREATE TABLE foo
// table exists: true
// >
let s1 = DispatchSemaphore(value: 0)
// table exists: true
// <
let s2 = DispatchSemaphore(value: 0)
// DROP TABLE foo
// table exists: false
// >
let s3 = DispatchSemaphore(value: 0)
// table exists: true
// <
let s4 = DispatchSemaphore(value: 0)
// table exists: false
let block1 = { () in
try! dbPool.writeWithoutTransaction { db in
try db.execute(sql: "CREATE TABLE foo(id INTEGER PRIMARY KEY)")
// warm cache
_ = try db.primaryKey("foo")
// cache contains the primary key
XCTAssertNotNil(db.schemaCache[.main].primaryKey("foo"))
s1.signal()
_ = s2.wait(timeout: .distantFuture)
try db.execute(sql: "DROP TABLE foo")
// cache does not contain the primary key
XCTAssertNil(db.schemaCache[.main].primaryKey("foo"))
s3.signal()
_ = s4.wait(timeout: .distantFuture)
// cache does not contain the primary key
XCTAssertNil(db.schemaCache[.main].primaryKey("foo"))
}
}
let block2 = { () in
_ = s1.wait(timeout: .distantFuture)
try! dbPool.read { db in
// activate snapshot isolation so that foo table is visible during the whole read. Any read is enough.
try db.makeStatement(sql: "SELECT * FROM sqlite_master").makeCursor().next()
// warm cache
_ = try db.primaryKey("foo")
// cache contains the primary key
XCTAssertNotNil(db.schemaCache[.main].primaryKey("foo"))
s2.signal()
_ = s3.wait(timeout: .distantFuture)
// cache contains the primary key
XCTAssertNotNil(db.schemaCache[.main].primaryKey("foo"))
// warm cache if needed
_ = try db.primaryKey("foo")
s4.signal()
}
}
let blocks = [block1, block2]
DispatchQueue.concurrentPerform(iterations: blocks.count) { index in
blocks[index]()
}
}
func testUnsafeReaderHasDatabaseCache() throws {
let dbPool = try makeDatabasePool()
try dbPool.write { db in
try db.execute(sql: "CREATE TABLE items (id INTEGER PRIMARY KEY, email TEXT UNIQUE, foo INT, bar DOUBLE)")
try db.execute(sql: "CREATE INDEX foobar ON items(foo, bar)")
}
try dbPool.unsafeRead { db in
// Assert that a cache is empty
XCTAssertTrue(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertTrue(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertTrue(db.schemaCache[.main].indexes(on: "items") == nil)
// Warm cache in a reader
_ = try db.primaryKey("items")
_ = try db.columns(in: "items")
_ = try db.indexes(on: "items")
// Assert that a reader cache is warmed
XCTAssertFalse(db.schemaCache[.main].primaryKey("items") == nil)
XCTAssertFalse(db.schemaCache[.main].columns(in: "items") == nil)
XCTAssertFalse(db.schemaCache[.main].indexes(on: "items") == nil)
}
}
}