diff --git a/Package.resolved b/Package.resolved index 3bcdc3a..d04b84b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift.git", "state" : { - "revision" : "ce63f697ed381383041d245b185af3c501de8bef", - "version" : "6.18.0" + "revision" : "e069e2732e3ee2b67bf89c1bda1937da0eaee7ef", + "version" : "6.24.2" } }, { diff --git a/Package.swift b/Package.swift index ab77fd4..a87c6b0 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,8 @@ import PackageDescription let package = Package( name: "SQLite", platforms: [ - .macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9), + .macOS(.v13), .macCatalyst(.v16), + .iOS(.v16), .tvOS(.v16), .watchOS(.v9), ], products: [ .library( @@ -19,7 +20,7 @@ let package = Package( ), .package( url: "https://github.com/groue/GRDB.swift.git", - from: "6.18.0" + from: "6.24.2" ), .package( url: "https://github.com/shareup/precise-iso-8601-date-formatter.git", diff --git a/Sources/SQLite/SQLiteDatabase.swift b/Sources/SQLite/SQLiteDatabase.swift index 3c1b5eb..4cb95d5 100644 --- a/Sources/SQLite/SQLiteDatabase.swift +++ b/Sources/SQLite/SQLiteDatabase.swift @@ -28,8 +28,14 @@ public final class SQLiteDatabase: DatabaseProtocol, @unchecked Sendable { path: String, busyTimeout: TimeInterval = 5 ) throws -> SQLiteDatabase { - guard path != ":memory:", let url = URL(string: path) - else { throw SQLiteError.SQLITE_IOERR } + guard path != ":memory:" else { + throw SQLiteError.SQLITE_IOERR + } + + let url: URL? = URL(string: path) + ?? URL(filePath: path, directoryHint: .notDirectory) + + guard let url else { throw SQLiteError.SQLITE_IOERR } let coordinator = NSFileCoordinator(filePresenter: nil) var fileCoordinatorError: NSError? @@ -636,11 +642,20 @@ private extension SQLiteDatabase { var config = Configuration() config.journalMode = isInMemory ? .default : .wal + // NOTE: GRDB recommends `defaultTransactionKind` be set + // to `.immediate` in order to prevent `SQLITE_BUSY` + // errors. Using `.immediate` appears to disable + // automatic vacuuming. + // + // https://swiftpackageindex.com/groue/grdb.swift/v6.24.2/documentation/grdb/databasesharing#How-to-limit-the-SQLITEBUSY-error + config.defaultTransactionKind = isInMemory + ? .deferred + : .immediate config.busyMode = .timeout(busyTimeout) config.observesSuspensionNotifications = true config.maximumReaderCount = max( ProcessInfo.processInfo.processorCount, - 5 + 6 ) guard !isInMemory else { diff --git a/Sources/SQLite/SQLiteValue.swift b/Sources/SQLite/SQLiteValue.swift index ef7d938..19707f2 100644 --- a/Sources/SQLite/SQLiteValue.swift +++ b/Sources/SQLite/SQLiteValue.swift @@ -17,7 +17,7 @@ extension [String: SQLiteValue] { init?(row: Row?) { guard let row else { return nil } var sqliteRow = SQLiteRow() - row.forEach { name, value in + for (name, value) in row { switch DatabaseValue(value: value)?.storage { case .none, .null: sqliteRow[name] = .null diff --git a/Tests/SQLiteTests/SQLiteDatabaseTests.swift b/Tests/SQLiteTests/SQLiteDatabaseTests.swift index 9f7015b..bf9c7a4 100644 --- a/Tests/SQLiteTests/SQLiteDatabaseTests.swift +++ b/Tests/SQLiteTests/SQLiteDatabaseTests.swift @@ -17,6 +17,16 @@ final class SQLiteDatabaseTests: XCTestCase { database = nil } + func testSQLiteVersionMeetsMinimumRequirements() throws { + try Sandbox.execute { directory in + let path = directory.appendingPathComponent("test.db").path + let db = try SQLiteDatabase(path: path) + defer { try? db.close() } + let version = try SQLiteVersion(db) + XCTAssert(version.isSupported) + } + } + func testDatabaseIsCreated() throws { try Sandbox.execute { directory in let path = directory.appendingPathComponent("test.db").path @@ -133,7 +143,7 @@ final class SQLiteDatabaseTests: XCTestCase { try database.execute(raw: _createTableWithBlob) try database.inTransaction { db in - try (0 ..< 1000).forEach { index in + for index in 0 ..< 1000 { let args: SQLiteArguments = [ "id": .integer(Int64(index)), "data": .data(_textData), ] @@ -162,7 +172,7 @@ final class SQLiteDatabaseTests: XCTestCase { try database.execute(raw: _createTableWithBlob) try database.inTransaction { db in - try (0 ..< 1000).forEach { index in + for index in 0 ..< 1000 { let args: SQLiteArguments = [ "id": .integer(Int64(index)), "data": .data(_textData), ] @@ -191,7 +201,7 @@ final class SQLiteDatabaseTests: XCTestCase { try database.execute(raw: _createTableWithBlob) try database.inTransaction { db in - try (0 ..< 1000).forEach { index in + for index in 0 ..< 1000 { let args: SQLiteArguments = [ "id": .integer(Int64(index)), "data": .data(_textData), ] @@ -220,7 +230,7 @@ final class SQLiteDatabaseTests: XCTestCase { try db1.execute(raw: _createTableWithBlob) try db1.inTransaction { db in - try (0 ..< 1000).forEach { index in + for index in 0 ..< 1000 { let args: SQLiteArguments = [ "id": .integer(Int64(index)), "data": .data(_textData), ] diff --git a/Tests/SQLiteTests/SQLitePublisherTests.swift b/Tests/SQLiteTests/SQLitePublisherTests.swift index 5593083..d50d292 100644 --- a/Tests/SQLiteTests/SQLitePublisherTests.swift +++ b/Tests/SQLiteTests/SQLitePublisherTests.swift @@ -16,11 +16,11 @@ final class SQLitePublisherTests: XCTestCase { try database.execute(raw: Person.createTable) try database.execute(raw: Pet.createTable) - try [_person1, _person2].forEach { person in + for person in [_person1, _person2] { try database.write(Person.insert, arguments: person.asArguments) } - try [_pet1, _pet2].forEach { pet in + for pet in [_pet1, _pet2] { try database.write(Pet.insert, arguments: pet.asArguments) } } @@ -104,7 +104,7 @@ final class SQLitePublisherTests: XCTestCase { defer { try? db.close() } try db.execute(raw: Person.createTable) - try [_person1, _person2].forEach { person in + for person in [_person1, _person2] { try db.write( Person.insert, arguments: person.asArguments diff --git a/Tests/SQLiteTests/SQLiteRow+ExtensionsTests.swift b/Tests/SQLiteTests/SQLiteRow+ExtensionsTests.swift index d4ccf0f..ae622f3 100644 --- a/Tests/SQLiteTests/SQLiteRow+ExtensionsTests.swift +++ b/Tests/SQLiteTests/SQLiteRow+ExtensionsTests.swift @@ -17,7 +17,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("negative", true), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -37,7 +37,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("nonempty", "123".data(using: .utf8)!), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -58,7 +58,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("2001", Date(timeIntervalSinceReferenceDate: 123)), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -80,7 +80,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("negative", -12_345.12345), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -102,7 +102,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("negative", -12_345), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -124,7 +124,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("negative", -12_345), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -144,7 +144,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("nonempty", "This is not empty"), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -165,7 +165,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -186,7 +186,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -209,7 +209,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("2001", Date(timeIntervalSinceReferenceDate: 123)), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -230,7 +230,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -251,7 +251,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -272,7 +272,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue, @@ -293,7 +293,7 @@ final class SQLiteRowExtensionsTests: XCTestCase { ("missing", nil), ] - try expected.forEach { key, expectedValue in + for (key, expectedValue) in expected { XCTAssertEqual(expectedValue, try values.value(for: key)) XCTAssertEqual( expectedValue,