226 lines
9.3 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|