Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/swift-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
10 changes: 2 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 1 addition & 9 deletions Sources/LSQLite/Connection/Connection+Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}()

Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 0 additions & 8 deletions Sources/LSQLite/Connection/Connection+Open.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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)
Expand All @@ -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
}()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 0 additions & 14 deletions Sources/LSQLite/Context/Context+Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 0 additions & 2 deletions Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions Tests/LSQLiteTests/Connection/Connection+OpenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
20 changes: 0 additions & 20 deletions Tests/LSQLiteTests/Context/Context+ResultTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpaquePointer?>?) {
Expand Down Expand Up @@ -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<OpaquePointer?>?) {
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()
}