import Foundation import Testing import DataLiteC import DataLiteCore struct ConnectionTests { @Test func testIsAutocommitInitially() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) #expect(connection.isAutocommit == true) } @Test func testIsAutocommitDuringTransaction() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) try connection.beginTransaction() #expect(connection.isAutocommit == false) } @Test func testIsAutocommitAfterCommit() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) try connection.beginTransaction() try connection.commitTransaction() #expect(connection.isAutocommit == true) } @Test func testIsAutocommitAfterRollback() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) try connection.beginTransaction() try connection.rollbackTransaction() #expect(connection.isAutocommit == true) } @Test(arguments: [ (Connection.Options.readwrite, false), (Connection.Options.readonly, true) ]) func testIsReadonly( _ opt: Connection.Options, _ isReadonly: Bool ) throws { let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("sqlite") defer { try? FileManager.default.removeItem(at: url) } let _ = try Connection( location: .file(path: url.path), options: [.create, .readwrite] ) let connection = try Connection( location: .file(path: url.path), options: [opt] ) #expect(connection.isReadonly == isReadonly) } @Test func testBusyTimeout() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) connection.busyTimeout = 5000 #expect(try connection.get(pragma: .busyTimeout) == 5000) try connection.set(pragma: .busyTimeout, value: 1000) #expect(connection.busyTimeout == 1000) } @Test func testBusyTimeoutSQLiteBusy() throws { let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("sqlite") defer { try? FileManager.default.removeItem(at: url) } let oneConn = try Connection( location: .file(path: url.path), options: [.create, .readwrite, .fullmutex] ) let twoConn = try Connection( location: .file(path: url.path), options: [.create, .readwrite, .fullmutex] ) try oneConn.execute(raw: """ CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT); """) try oneConn.beginTransaction() try oneConn.execute(raw: """ INSERT INTO test (value) VALUES ('first'); """) #expect( throws: Connection.Error( code: SQLITE_BUSY, message: "database is locked" ), performing: { twoConn.busyTimeout = 0 try twoConn.execute(raw: """ INSERT INTO test (value) VALUES ('second'); """) } ) try oneConn.rollbackTransaction() } @Test func testApplicationID() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) #expect(connection.applicationID == 0) connection.applicationID = 1024 #expect(try connection.get(pragma: .applicationID) == 1024) try connection.set(pragma: .applicationID, value: 123) #expect(connection.applicationID == 123) } @Test func testForeignKeys() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) #expect(connection.foreignKeys == false) connection.foreignKeys = true #expect(try connection.get(pragma: .foreignKeys) == true) try connection.set(pragma: .foreignKeys, value: false) #expect(connection.foreignKeys == false) } @Test func testJournalMode() throws { let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("sqlite") defer { try? FileManager.default.removeItem(at: url) } let connection = try Connection( location: .file(path: url.path), options: [.create, .readwrite] ) connection.journalMode = .delete #expect(try connection.get(pragma: .journalMode) == JournalMode.delete) try connection.set(pragma: .journalMode, value: JournalMode.wal) #expect(connection.journalMode == .wal) } @Test func testSynchronous() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) connection.synchronous = .normal #expect(try connection.get(pragma: .synchronous) == Synchronous.normal) try connection.set(pragma: .synchronous, value: Synchronous.full) #expect(connection.synchronous == .full) } @Test func testUserVersion() throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) connection.userVersion = 42 #expect(try connection.get(pragma: .userVersion) == 42) try connection.set(pragma: .userVersion, value: 13) #expect(connection.userVersion == 13) } @Test(arguments: [ (TestScalarFunc.self, TestScalarFunc.name), (TestAggregateFunc.self, TestAggregateFunc.name) ] as [(Function.Type, String)]) func testAddFunction( _ function: Function.Type, _ name: String ) throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) try connection.execute(sql: """ CREATE TABLE items (value INTEGER); INSERT INTO items (value) VALUES (1), (2), (NULL), (3); """) try connection.add(function: function) try connection.execute(raw: "SELECT \(name)(value) FROM items") } @Test(arguments: [ (TestScalarFunc.self, TestScalarFunc.name), (TestAggregateFunc.self, TestAggregateFunc.name) ] as [(Function.Type, String)]) func testRemoveFunction( _ function: Function.Type, _ name: String ) throws { let connection = try Connection( location: .inMemory, options: [.create, .readwrite] ) try connection.execute(sql: """ CREATE TABLE items (value INTEGER); INSERT INTO items (value) VALUES (1), (2), (NULL), (3); """) try connection.add(function: function) try connection.remove(function: function) #expect( throws: Connection.Error( code: SQLITE_ERROR, message: "no such function: \(name)" ), performing: { try connection.execute(raw: """ SELECT \(name)(value) FROM items """) } ) } } private extension ConnectionTests { final class TestScalarFunc: Function.Scalar { override class var argc: Int32 { 1 } override class var name: String { "TO_STR" } override class var options: Options { [.deterministic, .innocuous] } override class func invoke(args: Arguments) throws -> SQLiteRawRepresentable? { args[0].description } } final class TestAggregateFunc: Function.Aggregate { override class var argc: Int32 { 1 } override class var name: String { "MY_COUNT" } override class var options: Options { [.deterministic, .innocuous] } private var count: Int = 0 override func step(args: Arguments) throws { if args[0] != .null { count += 1 } } override func finalize() throws -> SQLiteRawRepresentable? { count } } }