DataRaft swift package

This commit is contained in:
2025-05-18 17:47:22 +03:00
parent d64c6b2ef2
commit 1b2cdaf23e
23 changed files with 1862 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
import Foundation
import Testing
import DataLiteC
import DataLiteCore
import DataRaft
class DatabaseServiceTests: DatabaseServiceKeyProvider {
private let keyOne = Connection.Key.rawKey(Data([
0xe8, 0xd7, 0x92, 0xa2, 0xa1, 0x35, 0x56, 0xc0,
0xfd, 0xbb, 0x2f, 0x91, 0xe8, 0x0b, 0x4b, 0x2a,
0xa2, 0xd7, 0x78, 0xe9, 0xe5, 0x87, 0x05, 0xb4,
0xe2, 0x1a, 0x42, 0x74, 0xee, 0xbc, 0x4c, 0x06
]))
private let keyTwo = Connection.Key.rawKey(Data([
0x9f, 0x45, 0x23, 0xbf, 0xfe, 0x11, 0x3e, 0x79,
0x42, 0x21, 0x48, 0x7c, 0xb6, 0xb1, 0xd5, 0x09,
0x34, 0x5f, 0xcb, 0x53, 0xa3, 0xdd, 0x8e, 0x41,
0x95, 0x27, 0xbb, 0x4e, 0x6e, 0xd8, 0xa7, 0x05
]))
private let fileURL: URL
private let service: DatabaseService
private lazy var currentKey = keyOne
init() throws {
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("sqlite")
let service = try DatabaseService(provider: {
try Connection(
path: fileURL.path,
options: [.create, .readwrite]
)
})
self.fileURL = fileURL
self.service = service
self.service.keyProvider = self
try self.service.perform { connection in
try connection.execute(sql: """
CREATE TABLE IF NOT EXISTS Item (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)
""")
}
}
deinit {
try? FileManager.default.removeItem(at: fileURL)
}
func databaseServiceKey(_ service: DatabaseService) throws -> Connection.Key {
currentKey
}
}
extension DatabaseServiceTests {
@Test func testSuccessPerformTransaction() throws {
try service.perform(in: .deferred) { connection in
#expect(connection.isAutocommit == false)
let stmt = try connection.prepare(
sql: "INSERT INTO Item (name) VALUES (?)",
options: []
)
try stmt.bind("Book", at: 1)
try stmt.step()
}
try service.perform { connection in
let stmt = try connection.prepare(
sql: "SELECT COUNT(*) FROM Item",
options: []
)
try stmt.step()
#expect(connection.isAutocommit)
#expect(stmt.columnValue(at: 0) == 1)
}
}
@Test func testNestedPerformTransaction() throws {
try service.perform(in: .deferred) { _ in
try service.perform(in: .deferred) { connection in
#expect(connection.isAutocommit == false)
let stmt = try connection.prepare(
sql: "INSERT INTO Item (name) VALUES (?)",
options: []
)
try stmt.bind("Book", at: 1)
try stmt.step()
}
}
try service.perform { connection in
let stmt = try connection.prepare(
sql: "SELECT COUNT(*) FROM Item",
options: []
)
try stmt.step()
#expect(connection.isAutocommit)
#expect(stmt.columnValue(at: 0) == 1)
}
}
@Test func testRollbackPerformTransaction() throws {
struct DummyError: Error, Equatable {}
#expect(throws: DummyError(), performing: {
try self.service.perform(in: .deferred) { connection in
#expect(connection.isAutocommit == false)
let stmt = try connection.prepare(
sql: "INSERT INTO Item (name) VALUES (?)",
options: []
)
try stmt.bind("Book", at: 1)
try stmt.step()
throw DummyError()
}
})
try service.perform { connection in
let stmt = try connection.prepare(
sql: "SELECT COUNT(*) FROM Item",
options: []
)
try stmt.step()
#expect(connection.isAutocommit)
#expect(stmt.columnValue(at: 0) == 0)
}
}
@Test func testSuccessReconnectPerformTransaction() throws {
let connection = try Connection(
path: fileURL.path,
options: [.readwrite]
)
try connection.apply(currentKey)
try connection.rekey(keyTwo)
currentKey = keyTwo
try service.perform(in: .deferred) { connection in
#expect(connection.isAutocommit == false)
let stmt = try connection.prepare(
sql: "INSERT INTO Item (name) VALUES (?)",
options: []
)
try stmt.bind("Book", at: 1)
try stmt.step()
}
try service.perform { connection in
let stmt = try connection.prepare(
sql: "SELECT COUNT(*) FROM Item",
options: []
)
try stmt.step()
#expect(stmt.columnValue(at: 0) == 1)
}
}
@Test func testFailReconnectPerformTransaction() throws {
let connection = try Connection(
path: fileURL.path,
options: [.readwrite]
)
try connection.apply(currentKey)
try connection.rekey(keyTwo)
let error = Connection.Error(
code: SQLITE_NOTADB,
message: "file is not a database"
)
#expect(throws: error, performing: {
try self.service.perform(in: .deferred) { connection in
#expect(connection.isAutocommit == false)
let stmt = try connection.prepare(
sql: "INSERT INTO Item (name) VALUES (?)",
options: []
)
try stmt.bind("Book", at: 1)
try stmt.step()
}
})
currentKey = keyTwo
try service.reconnect()
try service.perform { connection in
let stmt = try connection.prepare(
sql: "SELECT COUNT(*) FROM Item",
options: []
)
try stmt.step()
#expect(connection.isAutocommit)
#expect(stmt.columnValue(at: 0) == 0)
}
}
}

View File

@@ -0,0 +1,101 @@
import Testing
import DataLiteCore
@testable import DataRaft
@Suite struct MigrationServiceTests {
private typealias MigrationService = DataRaft.MigrationService<DatabaseService, VersionStorage>
private var connection: Connection!
private var migrationService: MigrationService!
init() throws {
let connection = try Connection(location: .inMemory, options: .readwrite)
self.connection = connection
self.migrationService = .init(service: .init(connection: connection), storage: .init())
}
@Test func addMigration() throws {
let migration1 = Migration<Int32>(version: 1, byResource: "migration_1", extension: "sql", in: .module)!
let migration2 = Migration<Int32>(version: 2, byResource: "migration_2", extension: "sql", in: .module)!
let migration3 = Migration<Int32>(version: 3, byResource: "migration_2", extension: "sql", in: .module)!
#expect(try migrationService.add(migration1) == ())
#expect(try migrationService.add(migration2) == ())
do {
try migrationService.add(migration3)
Issue.record("Expected duplicateMigration error for version \(migration3.version)")
} catch MigrationService.Error.duplicateMigration(let migration) {
#expect(migration == migration3)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test func migrate() throws {
let migration1 = Migration<Int32>(version: 1, byResource: "migration_1", extension: "sql", in: .module)!
let migration2 = Migration<Int32>(version: 2, byResource: "migration_2", extension: "sql", in: .module)!
try migrationService.add(migration1)
try migrationService.add(migration2)
try migrationService.migrate()
#expect(connection.userVersion == 2)
}
@Test func migrateWithError() throws {
let migration1 = Migration<Int32>(version: 1, byResource: "migration_1", extension: "sql", in: .module)!
let migration2 = Migration<Int32>(version: 2, byResource: "migration_2", extension: "sql", in: .module)!
let migration3 = Migration<Int32>(version: 3, byResource: "migration_3", extension: "sql", in: .module)!
try migrationService.add(migration1)
try migrationService.add(migration2)
try migrationService.add(migration3)
do {
try migrationService.migrate()
Issue.record("Expected migrationFailed error for version \(migration3.version)")
} catch MigrationService.Error.migrationFailed(let migration, _) {
#expect(migration == migration3)
} catch {
Issue.record("Unexpected error: \(error)")
}
#expect(connection.userVersion == 0)
}
@Test func migrateWithEmptyMigration() throws {
let migration1 = Migration<Int32>(version: 1, byResource: "migration_1", extension: "sql", in: .module)!
let migration2 = Migration<Int32>(version: 2, byResource: "migration_2", extension: "sql", in: .module)!
let migration4 = Migration<Int32>(version: 4, byResource: "migration_4", extension: "sql", in: .module)!
try migrationService.add(migration1)
try migrationService.add(migration2)
try migrationService.add(migration4)
do {
try migrationService.migrate()
Issue.record("Expected migrationFailed error for version \(migration4.version)")
} catch MigrationService.Error.emptyMigrationScript(let migration) {
#expect(migration == migration4)
} catch {
Issue.record("Unexpected error: \(error)")
}
#expect(connection.userVersion == 0)
}
}
private extension MigrationServiceTests {
struct VersionStorage: DataRaft.VersionStorage {
typealias Version = Int32
func getVersion(_ connection: Connection) throws -> Version {
connection.userVersion
}
func setVersion(_ connection: Connection, _ version: Version) throws {
connection.userVersion = version
}
}
}

View File

@@ -0,0 +1,64 @@
import Testing
import DataLiteCore
@testable import DataRaft
@Suite struct UserVersionStorageTests {
private var connection: Connection!
init() throws {
connection = try .init(location: .inMemory, options: .readwrite)
}
@Test func getVersion() throws {
connection.userVersion = 123
let storage = UserVersionStorage<Version>()
let version = try storage.getVersion(connection)
#expect(version == Version(rawValue: 123))
}
@Test func getVersionWithError() {
connection.userVersion = 123
let storage = UserVersionStorage<NilVersion>()
do {
_ = try storage.getVersion(connection)
Issue.record("Expected failure for invalid stored version")
} catch UserVersionStorage<NilVersion>.Error.invalidStoredVersion(let version) {
#expect(version == UInt32(bitPattern: connection.userVersion))
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test func setVersion() throws {
let storage = UserVersionStorage<Version>()
let version = Version(rawValue: 456)
try storage.setVersion(connection, version)
#expect(connection.userVersion == 456)
}
}
private extension UserVersionStorageTests {
struct Version: RawRepresentable, VersionRepresentable, Equatable {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
struct NilVersion: RawRepresentable, VersionRepresentable {
let rawValue: UInt32
init?(rawValue: UInt32) {
return nil
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
}