diff --git a/.github/workflows/swift-tests.yml b/.github/workflows/swift-tests.yml index d70924d..9ac964b 100644 --- a/.github/workflows/swift-tests.yml +++ b/.github/workflows/swift-tests.yml @@ -8,7 +8,7 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: swift-actions/setup-swift@v2 diff --git a/AGENTS.md b/AGENTS.md index 26da1ea..75e24e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ - Do not add documentation for protocol-derived requirements (such as `description`, `debugDescription`, or similar) or for the extension declarations themselves. ## Testing direction -- Prefer Swift Testing over XCTest for new or updated tests. Existing XCTest cases are legacy and should be migrated opportunistically. Keep tests runnable across Apple and non-Apple platforms. +- Use Swift Testing for tests. - Follow the Swift Testing style used in the blob tests: group by feature under `Tests/LSQLiteTests`, use `@Suite("...")`, and give `@Test` cases descriptive names. - Test files mirror source files with a `Tests` suffix, and each test file defines exactly one `@Suite` named after the original file (without the `Tests` suffix). - Use in-memory databases for isolation, set up shared fixtures in `init()` with `#require` on result codes and optional unwrapping, and tear down with `deinit` or `defer` when a handle must be closed. @@ -21,7 +21,7 @@ - Focus tests on validating the wrapper behavior and surface (rawValue round trips, ResultCode mapping, handle lifecycle), not SQLite's own functionality. ## Platform expectations -- Non-Apple platforms are fully supported. Gate Apple-only constants and behaviors with the appropriate `canImport` checks, and rely on the `MissedSwiftSQLite` target to expose any SQLite constants or helpers that the Swift importer misses on Linux. +- The Swift SQLite interface is generated automatically and can miss symbols; rely on the `MissedSwiftSQLite` target to expose missing constants or helpers. Use `@available` for version-specific differences. - Link and runtime assumptions should work with the system-provided `sqlite3` on each platform; avoid Apple-only or Darwin-specific APIs unless properly conditioned. ## Code organization diff --git a/Package.swift b/Package.swift index fd8ca87..f652185 100644 --- a/Package.swift +++ b/Package.swift @@ -13,16 +13,10 @@ let package = Package( targets: [ .target( name: "LSQLite", - dependencies: ["MissedSwiftSQLite"], - linkerSettings: [ - .linkedLibrary("sqlite3") - ] + dependencies: ["MissedSwiftSQLite"] ), .target( - name: "MissedSwiftSQLite", - linkerSettings: [ - .linkedLibrary("sqlite3") - ] + name: "MissedSwiftSQLite" ), .testTarget( name: "LSQLiteTests", diff --git a/Sources/LSQLite/Connection/Connection+Function.swift b/Sources/LSQLite/Connection/Connection+Function.swift index 593e0b0..99e56a4 100644 --- a/Sources/LSQLite/Connection/Connection+Function.swift +++ b/Sources/LSQLite/Connection/Connection+Function.swift @@ -91,7 +91,7 @@ extension Connection { /// Function flags describing determinism and security properties for user-defined SQL functions. /// - /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` + /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_SELFORDER1` @frozen public struct FunctionFlag: Hashable, OptionSet, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @@ -119,17 +119,11 @@ extension Connection { /// Related SQLite: `SQLITE_INNOCUOUS` public static let innocuous = Self(rawValue: SQLITE_INNOCUOUS) - /// Indicates the function may call `sqlite3_result_subtype`. - /// - /// Related SQLite: `SQLITE_RESULT_SUBTYPE` - public static let resultSubtype = Self(rawValue: SQLITE_RESULT_SUBTYPE) - private static let knownMask: UInt32 = { var mask = UInt32(bitPattern: Self.deterministic.rawValue) mask |= UInt32(bitPattern: Self.directOnly.rawValue) mask |= UInt32(bitPattern: Self.subtype.rawValue) mask |= UInt32(bitPattern: Self.innocuous.rawValue) - mask |= UInt32(bitPattern: Self.resultSubtype.rawValue) return mask }() @@ -143,7 +137,6 @@ extension Connection { if contains(.directOnly) { parts.append(".directOnly") } if contains(.subtype) { parts.append(".subtype") } if contains(.innocuous) { parts.append(".innocuous") } - if contains(.resultSubtype) { parts.append(".resultSubtype") } let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask @@ -161,7 +154,6 @@ extension Connection { if contains(.directOnly) { parts.append("SQLITE_DIRECTONLY") } if contains(.subtype) { parts.append("SQLITE_SUBTYPE") } if contains(.innocuous) { parts.append("SQLITE_INNOCUOUS") } - if contains(.resultSubtype) { parts.append("SQLITE_RESULT_SUBTYPE") } let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask diff --git a/Sources/LSQLite/Connection/Connection+Open.swift b/Sources/LSQLite/Connection/Connection+Open.swift index fb2934e..7277413 100644 --- a/Sources/LSQLite/Connection/Connection+Open.swift +++ b/Sources/LSQLite/Connection/Connection+Open.swift @@ -126,7 +126,6 @@ extension Connection { /// /// Related SQLite: `SQLITE_OPEN_WAL` public static let wal = Self(rawValue: SQLITE_OPEN_WAL) -#if canImport(Darwin) /// Applies complete file protection on Apple platforms. /// /// Related SQLite: `SQLITE_OPEN_FILEPROTECTION_COMPLETE` @@ -147,7 +146,6 @@ extension Connection { /// /// Related SQLite: `SQLITE_OPEN_FILEPROTECTION_MASK` public static let fileProtectionMask = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_MASK) -#endif private static let knownMask: UInt32 = { var mask = UInt32(bitPattern: Self.readonly.rawValue) @@ -170,13 +168,11 @@ extension Connection { mask |= UInt32(bitPattern: Self.sharedCache.rawValue) mask |= UInt32(bitPattern: Self.privateCache.rawValue) mask |= UInt32(bitPattern: Self.wal.rawValue) -#if canImport(Darwin) mask |= UInt32(bitPattern: Self.fileProtectionComplete.rawValue) mask |= UInt32(bitPattern: Self.fileProtectionCompleteUnlessOpen.rawValue) mask |= UInt32(bitPattern: Self.fileProtectionCompleteUntilFirstUserAuthentication.rawValue) mask |= UInt32(bitPattern: Self.fileProtectionNone.rawValue) mask |= UInt32(bitPattern: Self.fileProtectionMask.rawValue) -#endif return mask }() @@ -206,13 +202,11 @@ extension Connection { if contains(.sharedCache) { parts.append(".sharedCache") } if contains(.privateCache) { parts.append(".privateCache") } if contains(.wal) { parts.append(".wal") } -#if canImport(Darwin) if contains(.fileProtectionComplete) { parts.append(".fileProtectionComplete") } if contains(.fileProtectionCompleteUnlessOpen) { parts.append(".fileProtectionCompleteUnlessOpen") } if contains(.fileProtectionCompleteUntilFirstUserAuthentication) { parts.append(".fileProtectionCompleteUntilFirstUserAuthentication") } if contains(.fileProtectionNone) { parts.append(".fileProtectionNone") } if contains(.fileProtectionMask) { parts.append(".fileProtectionMask") } -#endif let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask @@ -246,13 +240,11 @@ extension Connection { if contains(.sharedCache) { parts.append("SQLITE_OPEN_SHAREDCACHE") } if contains(.privateCache) { parts.append("SQLITE_OPEN_PRIVATECACHE") } if contains(.wal) { parts.append("SQLITE_OPEN_WAL") } -#if canImport(Darwin) if contains(.fileProtectionComplete) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETE") } if contains(.fileProtectionCompleteUnlessOpen) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN") } if contains(.fileProtectionCompleteUntilFirstUserAuthentication) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION") } if contains(.fileProtectionNone) { parts.append("SQLITE_OPEN_FILEPROTECTION_NONE") } if contains(.fileProtectionMask) { parts.append("SQLITE_OPEN_FILEPROTECTION_MASK") } -#endif let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask diff --git a/Sources/LSQLite/Context/Context+Result.swift b/Sources/LSQLite/Context/Context+Result.swift index 9c5c4e8..9b2c1fe 100644 --- a/Sources/LSQLite/Context/Context+Result.swift +++ b/Sources/LSQLite/Context/Context+Result.swift @@ -163,18 +163,4 @@ extension Context { @inlinable public func resultZeroBlob(length: Int32) { sqlite3_result_zeroblob(rawValue, length) } - - /// Assigns an application-defined subtype to the current result value. - /// - /// Only the low 8 bits are preserved by SQLite. - /// Register the function with the result subtype flag or this may have no effect. - /// SQLite builds with strict subtype enforcement can raise an error without the flag. - /// - Parameter subtype: Subtype value to associate with the result. - /// - Returns: None. - /// - /// Related SQLite: `sqlite3_result_subtype`, `SQLITE_RESULT_SUBTYPE` - @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) - @inlinable public func resultSubtype(_ subtype: Subtype) { - sqlite3_result_subtype(rawValue, subtype.rawValue) - } } diff --git a/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift b/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift index 3610a42..defe58c 100644 --- a/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift +++ b/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift @@ -55,7 +55,6 @@ final class ConnectionFunctionTests { #expect(Connection.FunctionFlag.directOnly.rawValue == SQLITE_DIRECTONLY) #expect(Connection.FunctionFlag.subtype.rawValue == SQLITE_SUBTYPE) #expect(Connection.FunctionFlag.innocuous.rawValue == SQLITE_INNOCUOUS) - #expect(Connection.FunctionFlag.resultSubtype.rawValue == SQLITE_RESULT_SUBTYPE) } @Test("TextEncoding descriptions map values") @@ -77,7 +76,6 @@ final class ConnectionFunctionTests { | UInt32(bitPattern: Connection.FunctionFlag.directOnly.rawValue) | UInt32(bitPattern: Connection.FunctionFlag.subtype.rawValue) | UInt32(bitPattern: Connection.FunctionFlag.innocuous.rawValue) - | UInt32(bitPattern: Connection.FunctionFlag.resultSubtype.rawValue) let unknownOnlyRaw = Int32(bitPattern: ~knownMask) #expect(Connection.FunctionFlag(rawValue: unknownOnlyRaw).description == "unknown") let mixed = Connection.FunctionFlag(rawValue: Connection.FunctionFlag.deterministic.rawValue | unknownOnlyRaw) diff --git a/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift b/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift index ff95adc..15b4b2c 100644 --- a/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift +++ b/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift @@ -47,13 +47,11 @@ struct ConnectionOpenTests { #expect(Connection.OpenFlag.sharedCache.rawValue == SQLITE_OPEN_SHAREDCACHE) #expect(Connection.OpenFlag.privateCache.rawValue == SQLITE_OPEN_PRIVATECACHE) #expect(Connection.OpenFlag.wal.rawValue == SQLITE_OPEN_WAL) -#if canImport(Darwin) #expect(Connection.OpenFlag.fileProtectionComplete.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETE) #expect(Connection.OpenFlag.fileProtectionCompleteUnlessOpen.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN) #expect(Connection.OpenFlag.fileProtectionCompleteUntilFirstUserAuthentication.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION) #expect(Connection.OpenFlag.fileProtectionNone.rawValue == SQLITE_OPEN_FILEPROTECTION_NONE) #expect(Connection.OpenFlag.fileProtectionMask.rawValue == SQLITE_OPEN_FILEPROTECTION_MASK) -#endif } @Test("FileName description reflects rawValue") diff --git a/Tests/LSQLiteTests/Context/Context+ResultTests.swift b/Tests/LSQLiteTests/Context/Context+ResultTests.swift index 34ccf78..e937fe5 100644 --- a/Tests/LSQLiteTests/Context/Context+ResultTests.swift +++ b/Tests/LSQLiteTests/Context/Context+ResultTests.swift @@ -155,18 +155,6 @@ final class ContextResultTests { #expect(noMemoryResult != .row && noMemoryResult != .done) _ = preparedNoMem.finalize() } - - @Test("resultSubtype assigns subtype when available") - @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) - func resultSubtypeAssignsSubtype() throws { - #expect(connection.createFunction(name: "ctx_subtype", argumentCount: 0, textEncoding: .utf8, flags: [.resultSubtype], funcHandler: resultSubtypeHandler) == .ok) - var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_subtype()", for: connection) == .ok) - let prepared = try #require(statement) - #expect(prepared.step() == .row) - #expect(prepared.step() == .done) - #expect(prepared.finalize() == .ok) - } } private func resultIntHandler(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { @@ -254,14 +242,6 @@ private func resultNoMemoryHandler(_ context: OpaquePointer?, _ valueCount: Int3 Context(rawValue: context).resultNoMemoryError() } -@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) -private func resultSubtypeHandler(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { - guard let context else { return } - let wrapper = Context(rawValue: context) - wrapper.resultInt(1) - wrapper.resultSubtype(Subtype(rawValue: 7)) -} - private func resultBlobDestructor(_ blob: UnsafeMutableRawPointer?) { blob?.deallocate() }