├── .gitignore ├── Tests └── LSQLiteTests │ ├── Error.swift │ ├── RefWrappedValue.swift │ ├── DatabaseFileNameTests.swift │ ├── SendableTests.swift │ ├── ExecTests.swift │ ├── StatementTests.swift │ └── FunctionTests.swift ├── Sources ├── MissedSwiftSQLite │ ├── Directories.h │ ├── include │ │ └── MissedSwiftSQLite.h │ ├── Directories.c │ ├── Bind.h │ ├── Result.h │ ├── Result.c │ ├── Bind.c │ ├── ResultCode.h │ └── ResultCode.c └── LSQLite │ ├── Statement │ ├── Statement+Reset.swift │ ├── Statement+Finalize.swift │ ├── Statement+Readonly.swift │ ├── Statement+Database.swift │ ├── Statement+Busy.swift │ ├── Statement+Step.swift │ ├── Statement.swift │ ├── Statement+SQL.swift │ ├── Statement+Prepare.swift │ ├── Statement+Column.swift │ └── Statement+Bind.swift │ ├── Mutex │ ├── Mutex.swift │ ├── Mutex+Lock.swift │ └── Mutex+Lifecycle.swift │ ├── Database │ ├── Database+Interrupt.swift │ ├── Database+Autocommit.swift │ ├── Database+Mutex.swift │ ├── Database.swift │ ├── Database+Statement.swift │ ├── Database+Filename.swift │ ├── Database+LastInsertRowid.swift │ ├── Database+Changes.swift │ ├── Database+Close.swift │ ├── Database+Readonly.swift │ ├── Database+ProgressHandler.swift │ ├── Database+Blob.swift │ ├── Database+Exec.swift │ ├── Database+Error.swift │ ├── Database+Busy.swift │ ├── Database+Checkpoint.swift │ ├── Database+Trace.swift │ ├── Database+Collation.swift │ ├── Database+Open.swift │ ├── Database+Limit.swift │ ├── Database+Hooks.swift │ ├── Database+Function.swift │ ├── Database+Authorizer.swift │ └── Database+FileNameURI.swift │ ├── Context │ ├── Context+UserData.swift │ ├── Context+Database.swift │ ├── Context.swift │ ├── Context+Aggregate.swift │ ├── Context+Auxiliary.swift │ └── Context+Result.swift │ ├── Blob │ ├── Blob.swift │ ├── Blob+Lifecycle.swift │ └── Blob+Access.swift │ ├── Sleep.swift │ ├── Value │ ├── Value.swift │ ├── Value+Memory.swift │ ├── Value+Getters.swift │ └── Value+Introspect.swift │ ├── RowID.swift │ ├── SQLCompleteness.swift │ ├── Folders.swift │ ├── Randomness.swift │ ├── Subtype.swift │ ├── Initialization.swift │ ├── Datatype.swift │ ├── Memory.swift │ └── ResultCode.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── .github └── workflows │ └── swift-tests.yml ├── Package.swift ├── LICENSE ├── AGENTS.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | /.cache -------------------------------------------------------------------------------- /Tests/LSQLiteTests/Error.swift: -------------------------------------------------------------------------------- 1 | import LSQLite 2 | 3 | enum Error: Swift.Error { 4 | case result(ResultCode) 5 | case unknown 6 | } 7 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Directories.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | char *lsqlite_get_data_directory(void); 4 | void lsqlite_set_data_directory(char *value); 5 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/RefWrappedValue.swift: -------------------------------------------------------------------------------- 1 | class RefWrappedValue { 2 | var value: Value 3 | init(_ value: Value) { 4 | self.value = value 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/include/MissedSwiftSQLite.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "../Bind.h" 3 | #import "../Directories.h" 4 | #import "../Result.h" 5 | #import "../ResultCode.h" 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Directories.c: -------------------------------------------------------------------------------- 1 | #include "Directories.h" 2 | 3 | char *lsqlite_get_data_directory(void) { 4 | return sqlite3_data_directory; 5 | } 6 | 7 | void lsqlite_set_data_directory(char *value) { 8 | sqlite3_data_directory = value; 9 | } 10 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Reset.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Resets the prepared statement to run again from the beginning. 5 | /// 6 | /// Related SQLite: `sqlite3_reset` 7 | @inlinable public func reset() -> ResultCode { 8 | sqlite3_reset(rawValue).resultCode 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Finalize.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Finalizes and destroys the prepared statement handle. 5 | /// 6 | /// Related SQLite: `sqlite3_finalize` 7 | @inlinable public func finalize() -> ResultCode { 8 | sqlite3_finalize(rawValue).resultCode 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Readonly.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// True if the prepared statement does not directly modify the database. 5 | /// 6 | /// Related SQLite: `sqlite3_stmt_readonly` 7 | @inlinable public var isReadonly: Bool { 8 | sqlite3_stmt_readonly(rawValue) != 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Database.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Database connection that owns this prepared statement. 5 | /// 6 | /// Related SQLite: `sqlite3_db_handle` 7 | @inlinable public var database: Database? { 8 | return sqlite3_db_handle(rawValue).map(Database.init(rawValue:)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Mutex/Mutex.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around an SQLite mutex handle. 2 | /// 3 | /// Related SQLite: `sqlite3_mutex`, `sqlite3_mutex_alloc` 4 | @frozen public struct Mutex: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Busy.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// True if the statement has been stepped and not yet finished or reset. 5 | /// 6 | /// Related SQLite: `sqlite3_stmt_busy`, `sqlite3_step`, `sqlite3_reset` 7 | @inlinable public var isBusy: Bool { 8 | sqlite3_stmt_busy(rawValue) != 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Interrupt.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Requests that all running statements on this connection abort with `SQLITE_INTERRUPT`. 5 | /// 6 | /// Related SQLite: `sqlite3_interrupt`, `SQLITE_INTERRUPT` 7 | @inlinable public func interrupt() { 8 | sqlite3_interrupt(rawValue) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Step.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Steps the prepared statement once, returning the SQLite result code (row, done, or error). 5 | /// 6 | /// Related SQLite: `sqlite3_step`, `sqlite3_reset` 7 | @inlinable public func step() -> ResultCode { 8 | sqlite3_step(rawValue).resultCode 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context+UserData.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Context { 4 | /// Retrieves the user data pointer supplied when the function was registered. 5 | /// 6 | /// Related SQLite: `sqlite3_user_data`, `sqlite3_create_function_v2` 7 | @inlinable public var userData: UnsafeMutableRawPointer? { 8 | sqlite3_user_data(rawValue) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/swift-tests.yml: -------------------------------------------------------------------------------- 1 | name: Swift Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: swift-actions/setup-swift@v2 15 | with: 16 | swift-version: '6.2' 17 | - name: Run tests 18 | run: swift test 19 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Autocommit.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Indicates whether this connection is currently in autocommit mode (no transaction open). 5 | /// 6 | /// Related SQLite: `sqlite3_get_autocommit`, `BEGIN`, `COMMIT`, `ROLLBACK` 7 | @inlinable public var isAutocommit: Bool { 8 | sqlite3_get_autocommit(rawValue) != 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Mutex.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Mutex guarding this connection in serialized threading mode; `nil` otherwise. 5 | /// 6 | /// Related SQLite: `sqlite3_db_mutex`, `sqlite3_mutex`, `threading mode` 7 | @inlinable public var mutex: Mutex? { 8 | return sqlite3_db_mutex(rawValue).map(Mutex.init(rawValue:)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around a prepared SQLite statement handle. 2 | /// 3 | /// Related SQLite: `sqlite3_stmt`, `sqlite3_prepare_v2`, `sqlite3_finalize` 4 | @frozen public struct Statement: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Bind.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int lsqlite3_bind_transient_blob(sqlite3_stmt *db, int index, const void *blob, int length); 4 | int lsqlite3_bind_static_blob(sqlite3_stmt *db, int index, const void *blob, int length); 5 | 6 | int sqlite3_bind_transient_text(sqlite3_stmt *db, int index, const char *text, int length); 7 | int sqlite3_bind_static_text(sqlite3_stmt *db, int index, const char *text, int length); 8 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context+Database.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Context { 4 | /// Returns the database connection associated with this function context. 5 | /// 6 | /// Related SQLite: `sqlite3_context_db_handle`, `sqlite3_create_function_v2` 7 | @inlinable public var database: Database? { 8 | return sqlite3_context_db_handle(rawValue).map(Database.init(rawValue:)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Result.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | void lsqlite3_result_transient_blob(sqlite3_context *context, const void *blob, int length); 4 | void lsqlite3_result_static_blob(sqlite3_context *context, const void *blob, int length); 5 | 6 | void lsqlite3_result_transient_text(sqlite3_context *context, const char *text, int length); 7 | void lsqlite3_result_static_text(sqlite3_context *context, const char *text, int length); 8 | -------------------------------------------------------------------------------- /Sources/LSQLite/Blob/Blob.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around an open SQLite BLOB handle for incremental I/O. 2 | /// 3 | /// Related SQLite: `sqlite3_blob_open`, `sqlite3_blob_close`, `sqlite3_blob_read`, `sqlite3_blob_write`, `sqlite3_blob_bytes` 4 | @frozen public struct Blob: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Sleep.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Suspends the current thread for at least the given number of milliseconds using SQLite's VFS sleep hook. 4 | /// - Parameter milliseconds: Minimum time to pause. 5 | /// - Returns: The millisecond duration requested from the VFS. 6 | /// 7 | /// Related SQLite: `sqlite3_sleep`, `sqlite3_vfs.xSleep` 8 | @inlinable public func sleep(_ milliseconds: Int32) -> Int32 { 9 | sqlite3_sleep(milliseconds) 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around an SQLite database connection handle used by the higher-level `Database` API. 2 | /// 3 | /// Related SQLite: `sqlite3`, `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_close`, `sqlite3_close_v2` 4 | @frozen public struct Database: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Value/Value.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around an SQLite value object used for parameters, results, and introspection. 2 | /// 3 | /// Related SQLite: `sqlite3_value`, `sqlite3_value_dup`, `sqlite3_column_value`, `sqlite3_result_value`, `sqlite3_bind_value` 4 | @frozen public struct Value: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context.swift: -------------------------------------------------------------------------------- 1 | /// Wrapper around the SQLite function evaluation context passed into user-defined functions. 2 | /// 3 | /// Related SQLite: `sqlite3_context`, `sqlite3_result_*`, `sqlite3_user_data`, `sqlite3_aggregate_context`, `sqlite3_get_auxdata` 4 | @frozen public struct Context: RawRepresentable, @unchecked Sendable { 5 | public let rawValue: OpaquePointer 6 | 7 | @inlinable public init(rawValue: OpaquePointer) { 8 | self.rawValue = rawValue 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/RowID.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Strongly typed wrapper for SQLite `rowid` values. 4 | /// 5 | /// Related SQLite: `rowid`, `INTEGER PRIMARY KEY` 6 | @frozen public struct RowID: Equatable, RawRepresentable { 7 | public let rawValue: sqlite3_int64 8 | 9 | /// Creates a `RowID` from its raw 64-bit identifier. 10 | /// 11 | /// Related SQLite: `rowid` 12 | @inlinable public init(rawValue: sqlite3_int64) { 13 | self.rawValue = rawValue 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/LSQLite/SQLCompleteness.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Returns whether the UTF-8 SQL text ends with a complete statement (semicolon-aware trigger handling). 4 | /// - Parameter sql: Zero-terminated UTF-8 SQL source. 5 | /// - Returns: `true` if SQLite considers the SQL complete. 6 | /// 7 | /// Related SQLite: `sqlite3_complete`, `sqlite3_complete16`, `sqlite3_initialize` 8 | @inlinable public func sqlIsComplete(_ sql: UnsafePointer) -> Bool { 9 | sqlite3_complete(sql) != 0 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Statement.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Returns the next prepared statement on this connection after the given one, or the first when `statement` is `nil`. 5 | /// 6 | /// Related SQLite: `sqlite3_next_stmt`, `sqlite3_prepare_v2`, `sqlite3_finalize` 7 | @inlinable public func nextStatement(after statement: Statement?) -> Statement? { 8 | return sqlite3_next_stmt(rawValue, statement?.rawValue).map(Statement.init(rawValue:)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/LSQLite/Folders.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Global folder path used for relative database files when the Windows VFS is in play. 4 | /// Set `dataDirectory` once during startup; mutating it while connections are open can corrupt databases. 5 | /// 6 | /// Related SQLite: `sqlite3_data_directory`, `data_store_directory pragma` 7 | @inlinable public var dataDirectory: UnsafeMutablePointer? { 8 | get { 9 | lsqlite_get_data_directory() 10 | } 11 | set { 12 | lsqlite_set_data_directory(newValue) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/LSQLite/Randomness.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Fills a buffer with pseudo-random bytes from SQLite's internal generator (same as `random()`/`randomblob()`). 4 | /// - Parameters: 5 | /// - count: Number of bytes to write. 6 | /// - buffer: Destination buffer; must have space for `count` bytes. 7 | /// 8 | /// Related SQLite: `sqlite3_randomness`, `random()`, `randomblob()`, `sqlite3_vfs.xRandomness` 9 | @inlinable public func randomness(byteCount count: Int32, into buffer: UnsafeMutableRawPointer) { 10 | sqlite3_randomness(count, buffer) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/LSQLite/Subtype.swift: -------------------------------------------------------------------------------- 1 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 2 | /// Custom subtype tag associated with SQLite values. 3 | /// 4 | /// Related SQLite: `sqlite3_value_subtype`, `sqlite3_result_subtype` 5 | @frozen public struct Subtype: Equatable, RawRepresentable { 6 | public let rawValue: UInt32 7 | 8 | /// Creates a subtype tag from its raw numeric value. 9 | /// 10 | /// Related SQLite: `sqlite3_value_subtype`, `sqlite3_result_subtype` 11 | @inlinable public init(rawValue: UInt32) { 12 | self.rawValue = rawValue 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context+Aggregate.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Context { 4 | /// Provides per-aggregate state storage of the given size for this context. 5 | /// - Parameter size: Bytes to allocate on first call; pass `0` to fetch without allocating. 6 | /// - Returns: Stable buffer pointer for the aggregate instance, or `nil` on allocation failure. 7 | /// 8 | /// Related SQLite: `sqlite3_aggregate_context` 9 | @inlinable public func aggregateContext(size: Int32) -> UnsafeMutableRawPointer? { 10 | sqlite3_aggregate_context(rawValue, size) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Filename.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Returns the absolute filename for the named database on this connection. 5 | /// - Parameter name: Database name such as `"main"` or `"temp"`. 6 | /// - Returns: Absolute path, or `nil`/empty string for in-memory or temporary databases. 7 | /// 8 | /// Related SQLite: `sqlite3_db_filename`, `sqlite3_vfs.xFullPathname` 9 | @inlinable public func filename(forDatabaseNamed name: UnsafePointer) -> UnsafePointer? { 10 | sqlite3_db_filename(rawValue, name) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/LSQLite/Value/Value+Memory.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 4 | extension Value { 5 | /// Creates a protected copy of this value. 6 | /// 7 | /// Related SQLite: `sqlite3_value_dup` 8 | @inlinable public func createCopy() -> Value? { 9 | return sqlite3_value_dup(rawValue).map(Value.init(rawValue:)) 10 | } 11 | 12 | /// Releases a value copy created with sqlite3_value_dup. 13 | /// 14 | /// Related SQLite: `sqlite3_value_free` 15 | @inlinable public func free() { 16 | sqlite3_value_free(rawValue) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+SQL.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Original SQL text used to create the statement. 5 | /// 6 | /// Related SQLite: `sqlite3_sql` 7 | @inlinable public var sql: UnsafePointer? { 8 | sqlite3_sql(rawValue) 9 | } 10 | 11 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 12 | /// SQL text with bound parameters expanded; caller is responsible for freeing. 13 | /// 14 | /// Related SQLite: `sqlite3_expanded_sql` 15 | @inlinable public var expandedSql: UnsafeMutablePointer? { 16 | sqlite3_expanded_sql(rawValue) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/LSQLite/Initialization.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Initializes the SQLite library once for the process; normally invoked automatically but available for manual setup. 4 | /// - Returns: Result of `sqlite3_initialize`. 5 | /// 6 | /// Related SQLite: `sqlite3_initialize`, `sqlite3_os_init` 7 | @inlinable public func initialize() -> ResultCode { 8 | sqlite3_initialize().resultCode 9 | } 10 | 11 | /// Shuts down SQLite global state after all connections are closed. 12 | /// - Returns: Result of `sqlite3_shutdown`. 13 | /// 14 | /// Related SQLite: `sqlite3_shutdown`, `sqlite3_os_end` 15 | @inlinable public func shutdown() -> ResultCode { 16 | sqlite3_shutdown().resultCode 17 | } 18 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Result.c: -------------------------------------------------------------------------------- 1 | #include "Result.h" 2 | 3 | void lsqlite3_result_transient_blob(sqlite3_context *context, const void *blob, int length) { 4 | sqlite3_result_blob(context, blob, length, SQLITE_TRANSIENT); 5 | } 6 | 7 | void lsqlite3_result_static_blob(sqlite3_context *context, const void *blob, int length) { 8 | sqlite3_result_blob(context, blob, length, SQLITE_STATIC); 9 | } 10 | 11 | void lsqlite3_result_transient_text(sqlite3_context *context, const char *text, int length) { 12 | sqlite3_result_text(context, text, length, SQLITE_TRANSIENT); 13 | } 14 | 15 | void lsqlite3_result_static_text(sqlite3_context *context, const char *text, int length) { 16 | sqlite3_result_text(context, text, length, SQLITE_STATIC); 17 | } 18 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+LastInsertRowid.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Rowid of the most recent successful INSERT on this connection (rowid tables only). 5 | /// 6 | /// Related SQLite: `sqlite3_last_insert_rowid`, `last_insert_rowid()` 7 | @inlinable public func lastInsertedRowID() -> RowID { 8 | RowID(rawValue: sqlite3_last_insert_rowid(rawValue)) 9 | } 10 | 11 | /// Overrides the last-inserted rowid value reported for this connection. 12 | /// 13 | /// Related SQLite: `sqlite3_set_last_insert_rowid`, `sqlite3_last_insert_rowid` 14 | @inlinable public func setLastInsertedRowID(_ rowID: RowID) { 15 | sqlite3_set_last_insert_rowid(rawValue, rowID.rawValue) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/Bind.c: -------------------------------------------------------------------------------- 1 | #include "Bind.h" 2 | 3 | int lsqlite3_bind_transient_blob(sqlite3_stmt *db, int index, const void *blob, int length) { 4 | return sqlite3_bind_blob(db, index, blob, length, SQLITE_TRANSIENT); 5 | } 6 | 7 | int lsqlite3_bind_static_blob(sqlite3_stmt *db, int index, const void *blob, int length) { 8 | return sqlite3_bind_blob(db, index, blob, length, SQLITE_STATIC); 9 | } 10 | 11 | int sqlite3_bind_transient_text(sqlite3_stmt *db, int index, const char *text, int length) { 12 | return sqlite3_bind_text(db, index, text, length, SQLITE_TRANSIENT); 13 | } 14 | 15 | int sqlite3_bind_static_text(sqlite3_stmt *db, int index, const char *text, int length) { 16 | return sqlite3_bind_text(db, index, text, length, SQLITE_STATIC); 17 | } 18 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Changes.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Number of rows changed by the most recent INSERT, UPDATE, or DELETE on this connection (excludes trigger side-effects). 5 | /// 6 | /// Related SQLite: `sqlite3_changes`, `changes() SQL function`, `PRAGMA count_changes` 7 | @inlinable public var changes: Int32 { 8 | sqlite3_changes(rawValue) 9 | } 10 | 11 | /// Total number of rows inserted, updated, or deleted since this connection was opened (trigger work included). 12 | /// 13 | /// Related SQLite: `sqlite3_total_changes`, `sqlite3_changes`, `PRAGMA data_version`, `SQLITE_FCNTL_DATA_VERSION` 14 | @inlinable public var totalChanges: Int32 { 15 | sqlite3_total_changes(rawValue) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/LSQLite/Mutex/Mutex+Lock.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Mutex { 4 | /// Locks the mutex, blocking until it becomes available. 5 | /// 6 | /// Related SQLite: `sqlite3_mutex_enter`, `sqlite3_mutex_try` 7 | @inlinable public func enter() { 8 | sqlite3_mutex_enter(rawValue) 9 | } 10 | 11 | /// Attempts to lock the mutex without blocking; returns `.ok` or `.busy`. 12 | /// 13 | /// Related SQLite: `sqlite3_mutex_try` 14 | @inlinable public func `try`() -> ResultCode { 15 | sqlite3_mutex_try(rawValue).resultCode 16 | } 17 | 18 | /// Unlocks the mutex previously entered by this thread. 19 | /// 20 | /// Related SQLite: `sqlite3_mutex_leave` 21 | @inlinable public func leave() { 22 | sqlite3_mutex_leave(rawValue) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "LSQLite", 7 | products: [ 8 | .library( 9 | name: "LSQLite", 10 | targets: ["LSQLite"] 11 | ), 12 | .library( 13 | name: "MissedSwiftSQLite", 14 | targets: ["MissedSwiftSQLite"] 15 | ), 16 | ], 17 | targets: [ 18 | .target( 19 | name: "LSQLite", 20 | dependencies: ["MissedSwiftSQLite"] 21 | ), 22 | .target( 23 | name: "MissedSwiftSQLite", 24 | linkerSettings: [ 25 | .linkedLibrary("sqlite3") 26 | ] 27 | ), 28 | .testTarget( 29 | name: "LSQLiteTests", 30 | dependencies: ["LSQLite"] 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Sources/LSQLite/Blob/Blob+Lifecycle.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Blob { 4 | /// Moves this BLOB handle to a different row of the same table. 5 | /// - Parameter rowID: Target rowid. 6 | /// - Returns: Result of `sqlite3_blob_reopen`. 7 | /// 8 | /// Related SQLite: `sqlite3_blob_reopen`, `sqlite3_blob_read`, `sqlite3_blob_write`, `sqlite3_blob_bytes` 9 | @inlinable public func reopen(at rowID: RowID) -> ResultCode { 10 | sqlite3_blob_reopen(rawValue, rowID.rawValue).resultCode 11 | } 12 | 13 | /// Closes this BLOB handle; auto-commit transactions may finalize if no other writers remain. 14 | /// - Returns: Result of `sqlite3_blob_close`. 15 | /// 16 | /// Related SQLite: `sqlite3_blob_close`, `sqlite3_errcode`, `sqlite3_errmsg` 17 | @inlinable public func close() -> ResultCode { 18 | sqlite3_blob_close(rawValue).resultCode 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Close.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Closes the connection immediately; fails with `.busy` if statements, blobs, or backups are still open. 5 | /// - Returns: Result of `sqlite3_close`. 6 | /// 7 | /// Related SQLite: `sqlite3_close`, `sqlite3_finalize`, `sqlite3_blob_close`, `sqlite3_backup_finish` 8 | @inlinable public func close() -> ResultCode { 9 | sqlite3_close(rawValue).resultCode 10 | } 11 | 12 | /// Marks the connection for closure; remaining statements, blobs, or backups can finish before final teardown. 13 | /// - Returns: Result of `sqlite3_close_v2`. 14 | /// 15 | /// Related SQLite: `sqlite3_close_v2`, `sqlite3_finalize`, `sqlite3_blob_close`, `sqlite3_backup_finish` 16 | @available(iOS 8.2, macOS 10.10, tvOS 8.2, watchOS 2.0, *) 17 | @inlinable public func closeV2() -> ResultCode { 18 | sqlite3_close_v2(rawValue).resultCode 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context+Auxiliary.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Context { 4 | /// Destructor invoked when SQLite frees auxiliary data attached to a function argument. 5 | /// 6 | /// Related SQLite: `sqlite3_set_auxdata`, `sqlite3_get_auxdata` 7 | public typealias AuxiliaryDataDestructor = @convention(c) (UnsafeMutableRawPointer?) -> Void 8 | 9 | /// Returns per-argument auxiliary data previously set for the given parameter index. 10 | /// 11 | /// Related SQLite: `sqlite3_get_auxdata` 12 | @inlinable public func getAuxiliaryData(forArgument argument: Int32) -> UnsafeMutableRawPointer? { 13 | sqlite3_get_auxdata(rawValue, argument) 14 | } 15 | 16 | /// Attaches auxiliary data to the given parameter index with optional destructor. 17 | /// 18 | /// Related SQLite: `sqlite3_set_auxdata` 19 | @inlinable public func setAuxiliaryData(_ data: UnsafeMutableRawPointer?, forArgument argument: Int32, destructor: AuxiliaryDataDestructor? = nil) { 20 | sqlite3_set_auxdata(rawValue, argument, data, destructor) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 antonsergeev88 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/LSQLite/Value/Value+Getters.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Value { 4 | /// Pointer to the BLOB bytes for this value, or nil if not a BLOB. 5 | /// 6 | /// Related SQLite: `sqlite3_value_blob`, `sqlite3_value_bytes` 7 | @inlinable public var blob: UnsafeRawPointer? { 8 | sqlite3_value_blob(rawValue) 9 | } 10 | 11 | /// Value coerced to Double. 12 | /// 13 | /// Related SQLite: `sqlite3_value_double` 14 | @inlinable public var double: Double { 15 | sqlite3_value_double(rawValue) 16 | } 17 | 18 | /// Value coerced to 32-bit Int. 19 | /// 20 | /// Related SQLite: `sqlite3_value_int` 21 | @inlinable public var int: Int32 { 22 | sqlite3_value_int(rawValue) 23 | } 24 | 25 | /// Value coerced to 64-bit Int. 26 | /// 27 | /// Related SQLite: `sqlite3_value_int64` 28 | @inlinable public var int64: sqlite3_int64 { 29 | sqlite3_value_int64(rawValue) 30 | } 31 | 32 | /// UTF-8 text pointer for this value, or nil if not text; content may be invalidated by later calls. 33 | /// 34 | /// Related SQLite: `sqlite3_value_text`, `sqlite3_value_bytes` 35 | @inlinable public var text: UnsafePointer? { 36 | sqlite3_value_text(rawValue) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/LSQLite/Datatype.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Swift wrapper around SQLite's fundamental datatype codes used in value and column inspection. 4 | /// 5 | /// Related SQLite: `SQLITE_INTEGER`, `SQLITE_FLOAT`, `SQLITE_BLOB`, `SQLITE_NULL`, `SQLITE_TEXT`, `SQLITE3_TEXT` 6 | @frozen public struct Datatype: Equatable, RawRepresentable, CustomDebugStringConvertible { 7 | public let rawValue: Int32 8 | 9 | @inlinable public init(rawValue: Int32) { 10 | self.rawValue = rawValue 11 | } 12 | 13 | public static let integer = Self(rawValue: SQLITE_INTEGER) 14 | public static let float = Self(rawValue: SQLITE_FLOAT) 15 | public static let blob = Self(rawValue: SQLITE_BLOB) 16 | public static let null = Self(rawValue: SQLITE_NULL) 17 | public static let text = Self(rawValue: SQLITE_TEXT) 18 | 19 | /// Debug-friendly SQLite type name. 20 | /// 21 | /// Related SQLite: `SQLITE_INTEGER`, `SQLITE_FLOAT`, `SQLITE_BLOB`, `SQLITE_NULL`, `SQLITE_TEXT` 22 | public var debugDescription: String { 23 | switch self { 24 | case .integer: return "SQLITE_INTEGER" 25 | case .float: return "SQLITE_FLOAT" 26 | case .blob: return "SQLITE_BLOB" 27 | case .null: return "SQLITE_NULL" 28 | case .text: return "SQLITE_TEXT" 29 | default: return "Datatype(rawValue: \(rawValue))" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LSQLite/Blob/Blob+Access.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Blob { 4 | /// Size of this open BLOB in bytes. 5 | /// 6 | /// Related SQLite: `sqlite3_blob_bytes`, `sqlite3_blob_open`, `sqlite3_blob_close` 7 | @inlinable public var byteCount: Int32 { 8 | sqlite3_blob_bytes(rawValue) 9 | } 10 | 11 | /// Reads `length` bytes from the BLOB starting at `offset` into `buffer`. 12 | /// - Parameters: 13 | /// - buffer: Destination buffer. 14 | /// - length: Number of bytes to copy. 15 | /// - offset: Byte offset within the BLOB. 16 | /// - Returns: Result of `sqlite3_blob_read`. 17 | /// 18 | /// Related SQLite: `sqlite3_blob_read`, `sqlite3_blob_bytes`, `sqlite3_blob_open` 19 | @inlinable public func read(into buffer: UnsafeMutableRawPointer, length: Int32, offset: Int32) -> ResultCode { 20 | sqlite3_blob_read(rawValue, buffer, length, offset).resultCode 21 | } 22 | 23 | /// Writes `length` bytes from `buffer` into the BLOB starting at `offset`; handle must be opened for writing. 24 | /// - Parameters: 25 | /// - buffer: Source bytes to write. 26 | /// - length: Number of bytes to write. 27 | /// - offset: Byte offset within the BLOB. 28 | /// - Returns: Result of `sqlite3_blob_write`. 29 | /// 30 | /// Related SQLite: `sqlite3_blob_write`, `sqlite3_blob_bytes`, `sqlite3_blob_open` 31 | @inlinable public func write(_ buffer: UnsafeRawPointer, length: Int32, offset: Int32) -> ResultCode { 32 | sqlite3_blob_write(rawValue, buffer, length, offset).resultCode 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Readonly.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Read/write state values returned by `readWriteAccessState(forDatabaseNamed:)`. 5 | /// 6 | /// Related SQLite: `sqlite3_db_readonly` 7 | @frozen public struct ReadWriteAccessState: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | public static let noDatabase = Self(rawValue: -1) 15 | public static let readwrite = Self(rawValue: 0) 16 | public static let readonly = Self(rawValue: 1) 17 | 18 | /// Debug label for the access state. 19 | /// 20 | /// Related SQLite: `sqlite3_db_readonly` 21 | public var debugDescription: String { 22 | switch self { 23 | case .noDatabase: return "LSQLITE_NO_DATABASE" 24 | case .readwrite: return "LSQLITE_READWRITE)" 25 | case .readonly: return "LSQLITE_READONLY)" 26 | default: return "ReadWriteAccessState(rawValue: \(rawValue))" 27 | } 28 | } 29 | } 30 | 31 | /// Reports whether the named database is read-only, read/write, or missing on this connection. 32 | /// - Parameter name: Database name (e.g. `"main"` or an attached alias). 33 | /// - Returns: A `ReadWriteAccessState` describing access mode. 34 | /// 35 | /// Related SQLite: `sqlite3_db_readonly` 36 | @inlinable public func readWriteAccessState(forDatabaseNamed name: UnsafePointer) -> ReadWriteAccessState { 37 | ReadWriteAccessState(rawValue: sqlite3_db_readonly(rawValue, name)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Agent Guidelines for LSQLite 2 | 3 | ## Project intent 4 | - LSQLite is a zero-overhead, typed Swift wrapper over the SQLite C API. APIs should stay as 1:1 as possible with SQLite names and behavior, while replacing raw pointers and integer constants with small Swift wrappers. 5 | - Public APIs are thin `@inlinable` forwarders to the underlying `sqlite3_*` calls. Avoid adding higher-level abstractions, throwing error flows, or behavior changes beyond the existing SQLite semantics. 6 | - The wrapper types must always allow escape hatches: every wrapper should expose a `rawValue` that maps directly to the C handle or constant. 7 | - Inline documentation is first-class. New public symbols should include concise Swift doc comments plus a "Related SQLite:" section listing the original SQLite functions or constants they wrap. 8 | 9 | ## Testing direction 10 | - 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. 11 | 12 | ## Platform expectations 13 | - 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. 14 | - Link and runtime assumptions should work with the system-provided `sqlite3` on each platform; avoid Apple-only or Darwin-specific APIs unless properly conditioned. 15 | 16 | ## Code organization 17 | - Keep related wrappers in focused extension files, following the existing directory layout (e.g., `Database+Open`, `Statement+Bind`, `Value+Getters`). 18 | - Do not introduce new abstractions like ORMs or query builders. The surface should remain a typed reflection of the SQLite C API. 19 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/DatabaseFileNameTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import LSQLite 3 | 4 | @Suite 5 | struct DatabaseFileNameTests { 6 | @Test 7 | func constantsHaveExpectedRawValues() { 8 | #expect(Database.FileName.memory.rawValue == ":memory:") 9 | #expect(Database.FileName.temporary.rawValue == "") 10 | } 11 | 12 | @Test 13 | func uriFilenamesMatchSQLiteExamples() { 14 | #expect(Database.FileName.uri(path: .init(rawValue: "data.db")).rawValue == "file:data.db") 15 | #expect( 16 | Database.FileName.uri(authority: .empty, path: .init(rawValue: "/home/fred/data.db")).rawValue 17 | == "file:///home/fred/data.db" 18 | ) 19 | #expect( 20 | Database.FileName.uri(authority: .localhost, path: .init(rawValue: "/home/fred/data.db")).rawValue 21 | == "file://localhost/home/fred/data.db" 22 | ) 23 | #expect( 24 | Database.FileName.uri(authority: .localhost, path: .init(rawValue: "home/fred/data.db")).rawValue 25 | == "file://localhost/home/fred/data.db" 26 | ) 27 | #expect( 28 | Database.FileName.uri( 29 | path: .init(rawValue: "data.db"), 30 | query: .init([.mode(.ro), .cache(.private)]) 31 | ).rawValue == "file:data.db?mode=ro&cache=private" 32 | ) 33 | } 34 | 35 | @Test 36 | func uriComponentInitializersPercentEncodeReservedDelimiters() { 37 | #expect(Database.FileName.URI.Path("data?.db#x%y").rawValue == "data%3F.db%23x%25y") 38 | #expect(Database.FileName.URI.QueryKey("a&b=c#d%").rawValue == "a%26b%3Dc%23d%25") 39 | #expect(Database.FileName.URI.QueryValue("a&b=c#d%").rawValue == "a%26b%3Dc%23d%25") 40 | #expect(Database.FileName.URI.Fragment("a%b").rawValue == "a%25b") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/LSQLite/Value/Value+Introspect.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Value { 4 | /// Number of bytes in the value for BLOB or text; 0 for other types. 5 | /// 6 | /// Related SQLite: `sqlite3_value_bytes`, `sqlite3_value_bytes16` 7 | @inlinable public func byteCount() -> Int32 { 8 | sqlite3_value_bytes(rawValue) 9 | } 10 | 11 | /// Datatype code for this value. 12 | /// 13 | /// Related SQLite: `sqlite3_value_type` 14 | @inlinable public var type: Datatype { 15 | Datatype(rawValue: sqlite3_value_type(rawValue)) 16 | } 17 | 18 | /// Applies numeric affinity and returns the resulting type code. 19 | /// 20 | /// Related SQLite: `sqlite3_value_numeric_type` 21 | @inlinable public func convertToNumericType() -> Datatype { 22 | Datatype(rawValue: sqlite3_value_numeric_type(rawValue)) 23 | } 24 | 25 | @available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) 26 | /// Whether the column value is marked as unchanged during an UPDATE. 27 | /// 28 | /// Related SQLite: `sqlite3_value_nochange` 29 | @inlinable public var noChange: Bool { 30 | sqlite3_value_nochange(rawValue) != 0 31 | } 32 | 33 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 34 | /// Indicates that the value originated from a bound parameter rather than stored data. 35 | /// 36 | /// Related SQLite: `sqlite3_value_frombind` 37 | @inlinable public var isFromBind: Bool { 38 | sqlite3_value_frombind(rawValue) != 0 39 | } 40 | 41 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 42 | /// User-defined subtype associated with this value. 43 | /// 44 | /// Related SQLite: `sqlite3_value_subtype`, `sqlite3_result_subtype` 45 | @inlinable public var subtype: Subtype { 46 | Subtype(rawValue: sqlite3_value_subtype(rawValue)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+ProgressHandler.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Callback invoked periodically during virtual machine execution; return a code indicating whether to continue. 5 | /// 6 | /// Related SQLite: `sqlite3_progress_handler` 7 | public typealias ProgressHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Int32 8 | 9 | /// Return codes from a progress handler to keep running or interrupt. 10 | /// 11 | /// Related SQLite: `sqlite3_progress_handler`, `SQLITE_INTERRUPT` 12 | @frozen public struct ProgressHandlerResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 13 | public let rawValue: Int32 14 | 15 | @inlinable public init(rawValue: Int32) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | public static let `continue` = Self(rawValue: 0) 20 | public static let interrupt = Self(rawValue: 1) 21 | 22 | /// Debug label for the progress handler result. 23 | /// 24 | /// Related SQLite: `sqlite3_progress_handler`, `SQLITE_INTERRUPT` 25 | public var debugDescription: String { 26 | switch self { 27 | case .continue: return "LSQLITE_CONTINUE" 28 | case .interrupt: return "LSQLITE_INTERRUPT" 29 | default: return "ProgressHandlerResult(rawValue: \(rawValue))" 30 | } 31 | } 32 | } 33 | 34 | /// Registers a progress handler invoked every `instructionCount` virtual machine steps; return `.interrupt` to halt the operation. 35 | /// - Parameters: 36 | /// - instructionCount: Approximate number of VDBE instructions between callbacks; `<= 0` disables the handler. 37 | /// - userData: Custom context passed to the handler. 38 | /// - handler: Progress callback; `nil` to clear. 39 | /// 40 | /// Related SQLite: `sqlite3_progress_handler`, `sqlite3_exec`, `sqlite3_step` 41 | @inlinable public func setProgressHandler(instructionCount: Int32, userData: UnsafeMutableRawPointer? = nil, handler: ProgressHandler? = nil) { 42 | sqlite3_progress_handler(rawValue, instructionCount, handler, userData) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Blob.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Access flags for incremental BLOB I/O opened via `openBlob(_:databaseName:tableName:columnName:rowID:flags:)`. 5 | /// 6 | /// Related SQLite: `sqlite3_blob_open` 7 | @frozen public struct OpenBlobFlag: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | public static let readonly = Self(rawValue: 0) 15 | public static let readwrite = Self(rawValue: 1) 16 | 17 | /// Debug label for blob open flags. 18 | /// 19 | /// Related SQLite: `sqlite3_blob_open` 20 | public var debugDescription: String { 21 | switch self { 22 | case .readonly: return "LSQLITE_READONLY" 23 | case .readwrite: return "LSQLITE_READWRITE" 24 | default: return "OpenBlobFlag(rawValue: \(rawValue))" 25 | } 26 | } 27 | } 28 | 29 | /// Opens a blob handle for incremental I/O against a specific row/column. 30 | /// - Parameters: 31 | /// - blob: Output blob handle. 32 | /// - databaseName: Database name (`"main"`, `"temp"`, or attached name). 33 | /// - tableName: Table owning the BLOB column. 34 | /// - columnName: Column holding the BLOB/TEXT value. 35 | /// - rowID: Rowid to target. 36 | /// - flags: Access mode for the handle. 37 | /// - Returns: Result of `sqlite3_blob_open`. 38 | /// 39 | /// Related SQLite: `sqlite3_blob_open`, `sqlite3_blob_read`, `sqlite3_blob_write`, `sqlite3_blob_reopen`, `sqlite3_blob_close` 40 | @inlinable public func openBlob(_ blob: inout Blob?, databaseName: UnsafePointer, tableName: UnsafePointer, columnName: UnsafePointer, rowID: RowID, flags: OpenBlobFlag) -> ResultCode { 41 | var blobPointer: OpaquePointer? = nil 42 | let resultCode = sqlite3_blob_open(rawValue, databaseName, tableName, columnName, rowID.rawValue, flags.rawValue, &blobPointer).resultCode 43 | blob = blobPointer.map(Blob.init(rawValue:)) 44 | return resultCode 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/SendableTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import LSQLite 3 | import MissedSwiftSQLite 4 | 5 | @Suite 6 | struct SendableTests { 7 | private func assertSendable(_ value: T) { 8 | _ = value 9 | } 10 | 11 | @Test 12 | func pointerWrappersConformToSendable() { 13 | assertSendable(Database(rawValue: OpaquePointer(bitPattern: 0x1)!)) 14 | assertSendable(Blob(rawValue: OpaquePointer(bitPattern: 0x2)!)) 15 | assertSendable(Context(rawValue: OpaquePointer(bitPattern: 0x3)!)) 16 | assertSendable(Mutex(rawValue: OpaquePointer(bitPattern: 0x4)!)) 17 | assertSendable(Statement(rawValue: OpaquePointer(bitPattern: 0x5)!)) 18 | assertSendable(Value(rawValue: OpaquePointer(bitPattern: 0x6)!)) 19 | } 20 | 21 | @Test 22 | func pointerWrappersMoveAcrossDetachedTasks() async { 23 | let database = Database(rawValue: OpaquePointer(bitPattern: 0x10)!) 24 | let blob = Blob(rawValue: OpaquePointer(bitPattern: 0x11)!) 25 | let context = Context(rawValue: OpaquePointer(bitPattern: 0x12)!) 26 | let mutex = Mutex(rawValue: OpaquePointer(bitPattern: 0x13)!) 27 | let statement = Statement(rawValue: OpaquePointer(bitPattern: 0x14)!) 28 | let value = Value(rawValue: OpaquePointer(bitPattern: 0x15)!) 29 | 30 | let detachedDatabase = await Task.detached { database }.value 31 | let detachedBlob = await Task.detached { blob }.value 32 | let detachedContext = await Task.detached { context }.value 33 | let detachedMutex = await Task.detached { mutex }.value 34 | let detachedStatement = await Task.detached { statement }.value 35 | let detachedValue = await Task.detached { value }.value 36 | 37 | #expect(Int(bitPattern: detachedDatabase.rawValue) == Int(bitPattern: database.rawValue)) 38 | #expect(Int(bitPattern: detachedBlob.rawValue) == Int(bitPattern: blob.rawValue)) 39 | #expect(Int(bitPattern: detachedContext.rawValue) == Int(bitPattern: context.rawValue)) 40 | #expect(Int(bitPattern: detachedMutex.rawValue) == Int(bitPattern: mutex.rawValue)) 41 | #expect(Int(bitPattern: detachedStatement.rawValue) == Int(bitPattern: statement.rawValue)) 42 | #expect(Int(bitPattern: detachedValue.rawValue) == Int(bitPattern: value.rawValue)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Exec.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Row callback invoked by `exec(_:)` when SQL produces result rows. 5 | /// 6 | /// Related SQLite: `sqlite3_exec` 7 | public typealias ExecCallback = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ count: Int32, _ values: UnsafeMutablePointer?>?, _ columns: UnsafeMutablePointer?>?) -> Int32 8 | 9 | /// Return codes for `ExecCallback` indicating whether to continue or abort execution. 10 | /// 11 | /// Related SQLite: `sqlite3_exec` 12 | @frozen public struct ExecCallbackResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 13 | public let rawValue: Int32 14 | 15 | @inlinable public init(rawValue: Int32) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | public static let `continue` = Self(rawValue: 0) 20 | public static let abort = Self(rawValue: 1) 21 | 22 | /// Debug label for the exec callback result. 23 | /// 24 | /// Related SQLite: `sqlite3_exec` 25 | public var debugDescription: String { 26 | switch self { 27 | case .continue: return "LSQLITE_CONTINUE" 28 | case .abort: return "LSQLITE_ABORT" 29 | default: return "ExecCallbackResult(rawValue: \(rawValue))" 30 | } 31 | } 32 | } 33 | 34 | /// Executes one or more semicolon-separated SQL statements with an optional row callback. 35 | /// - Parameters: 36 | /// - sql: UTF-8 SQL text to run. 37 | /// - errorMessage: Optional storage for an allocated error message; caller should free on error. 38 | /// - userData: Custom context passed into each callback invocation. 39 | /// - callback: Row callback returning `ExecCallbackResult`; `nil` to ignore rows. 40 | /// - Returns: Result of `sqlite3_exec`. 41 | /// 42 | /// Related SQLite: `sqlite3_exec`, `sqlite3_prepare_v2`, `sqlite3_step`, `sqlite3_finalize` 43 | @inlinable public func exec(_ sql: UnsafePointer, errorMessage: UnsafeMutablePointer?>? = nil, userData: UnsafeMutableRawPointer? = nil, callback: ExecCallback? = nil) -> ResultCode { 44 | sqlite3_exec(rawValue, sql, callback, userData, errorMessage).resultCode 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Error.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Switch controlling whether extended result codes are reported for this connection. 5 | /// 6 | /// Related SQLite: `sqlite3_extended_result_codes` 7 | @frozen public struct ExtendedResultCodeStatus: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | public static let off = Self(rawValue: 0) 15 | public static let on = Self(rawValue: 1) 16 | 17 | /// Debug label for the extended result code toggle. 18 | /// 19 | /// Related SQLite: `sqlite3_extended_result_codes` 20 | public var debugDescription: String { 21 | switch self { 22 | case .off: return "LSQLITE_OFF" 23 | case .on: return "LSQLITE_ON" 24 | default: return "ExtendedResultCodeStatus(rawValue: \(rawValue))" 25 | } 26 | } 27 | } 28 | 29 | /// Last result code set by an API call on this connection. 30 | /// 31 | /// Related SQLite: `sqlite3_errcode`, `sqlite3_errmsg`, `sqlite3_errmsg16` 32 | @inlinable public var lastErrorCode: ResultCode { 33 | sqlite3_errcode(rawValue).resultCode 34 | } 35 | 36 | /// Last extended result code set by an API call on this connection. 37 | /// 38 | /// Related SQLite: `sqlite3_extended_errcode`, `sqlite3_errcode` 39 | @inlinable public var lastExtendedErrorCode: ResultCode { 40 | sqlite3_extended_errcode(rawValue).resultCode 41 | } 42 | 43 | /// UTF-8 message text for the most recent API call on this connection. 44 | /// 45 | /// Related SQLite: `sqlite3_errmsg`, `sqlite3_errmsg16`, `sqlite3_errstr` 46 | @inlinable public var lastErrorMessage: UnsafePointer { 47 | sqlite3_errmsg(rawValue) 48 | } 49 | 50 | /// Enables (`.on`) or disables (`.off`) extended result codes for this connection. 51 | /// - Parameter status: Toggle for extended result codes. 52 | /// - Returns: Result of `sqlite3_extended_result_codes`. 53 | /// 54 | /// Related SQLite: `sqlite3_extended_result_codes`, `sqlite3_extended_errcode`, `sqlite3_errcode` 55 | @inlinable public func setExtendedResultCodes(_ status: ExtendedResultCodeStatus) -> ResultCode { 56 | sqlite3_extended_result_codes(rawValue, status.rawValue).resultCode 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Busy.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Callback invoked when SQLite reports a busy contention; return a retry decision. 5 | /// 6 | /// Related SQLite: `sqlite3_busy_handler`, `sqlite3_busy_timeout` 7 | public typealias BusyHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ attempt: Int32) -> Int32 8 | 9 | /// Result codes a busy handler can return to retry or abort. 10 | /// 11 | /// Related SQLite: `sqlite3_busy_handler`, `sqlite3_busy_timeout`, `SQLITE_BUSY` 12 | @frozen public struct BusyHandlerResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 13 | public let rawValue: Int32 14 | 15 | @inlinable public init(rawValue: Int32) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | public static let `break` = Self(rawValue: 0) 20 | public static let `continue` = Self(rawValue: 1) 21 | 22 | /// Debug label for the busy handler response. 23 | /// 24 | /// Related SQLite: `sqlite3_busy_handler`, `SQLITE_BUSY` 25 | public var debugDescription: String { 26 | switch self { 27 | case .break: return "LSQLITE_BREAK" 28 | case .continue: return "LSQLITE_CONTINUE" 29 | default: return "BusyHandlerResult(rawValue: \(rawValue))" 30 | } 31 | } 32 | } 33 | 34 | /// Registers a busy handler invoked when this connection hits `SQLITE_BUSY`; return `.continue` to retry or `.break` to stop. 35 | /// - Parameters: 36 | /// - userData: Custom pointer passed into the handler. 37 | /// - handler: Busy callback; pass `nil` to clear. 38 | /// - Returns: Result of `sqlite3_busy_handler`. 39 | /// 40 | /// Related SQLite: `sqlite3_busy_handler`, `SQLITE_BUSY`, `sqlite3_busy_timeout`, `PRAGMA busy_timeout` 41 | @inlinable public func setBusyHandler(userData: UnsafeMutableRawPointer? = nil, _ handler: BusyHandler? = nil) -> ResultCode { 42 | sqlite3_busy_handler(rawValue, handler, userData).resultCode 43 | } 44 | 45 | /// Sets a default busy handler that sleeps and retries for the given duration, or clears handlers when `milliseconds <= 0`. 46 | /// - Parameter milliseconds: Total time to keep retrying before returning `SQLITE_BUSY`. 47 | /// 48 | /// Related SQLite: `sqlite3_busy_timeout`, `sqlite3_busy_handler`, `PRAGMA busy_timeout` 49 | @inlinable public func setTimerBusyHandler(milliseconds: Int32) { 50 | sqlite3_busy_timeout(rawValue, milliseconds) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Prepare.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Flags for sqlite3_prepare_v3 prepFlags argument. 5 | /// 6 | /// Related SQLite: `sqlite3_prepare_v3`, `SQLITE_PREPARE_PERSISTENT`, `SQLITE_PREPARE_NORMALIZE`, `SQLITE_PREPARE_NO_VTAB` 7 | @frozen public struct PrepareFlag: OptionSet { 8 | public let rawValue: UInt32 9 | 10 | @inlinable public init(rawValue: UInt32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | /// Hint that the statement will be reused; SQLite may avoid lookaside allocations. 15 | /// 16 | /// Related SQLite: `SQLITE_PREPARE_PERSISTENT` 17 | public static let persistent = Self(rawValue: UInt32(SQLITE_PREPARE_PERSISTENT)) 18 | /// Normalization flag (currently a no-op). 19 | /// 20 | /// Related SQLite: `SQLITE_PREPARE_NORMALIZE` 21 | public static let normalize = Self(rawValue: UInt32(SQLITE_PREPARE_NORMALIZE)) 22 | /// Causes preparation to fail if the SQL uses virtual tables. 23 | /// 24 | /// Related SQLite: `SQLITE_PREPARE_NO_VTAB` 25 | public static let noVTab = Self(rawValue: UInt32(SQLITE_PREPARE_NO_VTAB)) 26 | } 27 | 28 | /// Compiles UTF-8 SQL into a prepared statement using sqlite3_prepare_v2. 29 | /// 30 | /// Related SQLite: `sqlite3_prepare_v2`, `sqlite3_finalize`, `sqlite3_step` 31 | @inlinable public static func prepare(_ statement: inout Statement?, sql: UnsafePointer, tail: UnsafeMutablePointer?>? = nil, for database: Database) -> ResultCode { 32 | var statementPointer: OpaquePointer? = nil 33 | let resultCode = sqlite3_prepare_v2(database.rawValue, sql, -1, &statementPointer, tail).resultCode 34 | statement = statementPointer.map(Statement.init(rawValue:)) 35 | return resultCode 36 | } 37 | 38 | @available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) 39 | /// Compiles UTF-8 SQL with sqlite3_prepare_v3 using the provided flags. 40 | /// 41 | /// Related SQLite: `sqlite3_prepare_v3`, `sqlite3_finalize`, `sqlite3_step` 42 | @inlinable public static func prepare(_ statement: inout Statement?, sql: UnsafePointer, tail: UnsafeMutablePointer?>? = nil, for database: Database, prepareFlag: PrepareFlag) -> ResultCode { 43 | var statementPointer: OpaquePointer? = nil 44 | let resultCode = sqlite3_prepare_v3(database.rawValue, sql, -1, prepareFlag.rawValue, &statementPointer, tail).resultCode 45 | statement = statementPointer.map(Statement.init(rawValue:)) 46 | return resultCode 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/ResultCode.h: -------------------------------------------------------------------------------- 1 | extern const int LSQLITE_ERROR_MISSING_COLLSEQ; 2 | extern const int LSQLITE_ERROR_RETRY; 3 | extern const int LSQLITE_ERROR_SNAPSHOT; 4 | extern const int LSQLITE_IOERR_READ; 5 | extern const int LSQLITE_IOERR_SHORT_READ; 6 | extern const int LSQLITE_IOERR_WRITE; 7 | extern const int LSQLITE_IOERR_FSYNC; 8 | extern const int LSQLITE_IOERR_DIR_FSYNC; 9 | extern const int LSQLITE_IOERR_TRUNCATE; 10 | extern const int LSQLITE_IOERR_FSTAT; 11 | extern const int LSQLITE_IOERR_UNLOCK; 12 | extern const int LSQLITE_IOERR_RDLOCK; 13 | extern const int LSQLITE_IOERR_DELETE; 14 | extern const int LSQLITE_IOERR_BLOCKED; 15 | extern const int LSQLITE_IOERR_NOMEM; 16 | extern const int LSQLITE_IOERR_ACCESS; 17 | extern const int LSQLITE_IOERR_CHECKRESERVEDLOCK; 18 | extern const int LSQLITE_IOERR_LOCK; 19 | extern const int LSQLITE_IOERR_CLOSE; 20 | extern const int LSQLITE_IOERR_DIR_CLOSE; 21 | extern const int LSQLITE_IOERR_SHMOPEN; 22 | extern const int LSQLITE_IOERR_SHMSIZE; 23 | extern const int LSQLITE_IOERR_SHMLOCK; 24 | extern const int LSQLITE_IOERR_SHMMAP; 25 | extern const int LSQLITE_IOERR_SEEK; 26 | extern const int LSQLITE_IOERR_DELETE_NOENT; 27 | extern const int LSQLITE_IOERR_MMAP; 28 | extern const int LSQLITE_IOERR_GETTEMPPATH; 29 | extern const int LSQLITE_IOERR_CONVPATH; 30 | extern const int LSQLITE_IOERR_VNODE; 31 | extern const int LSQLITE_IOERR_AUTH; 32 | extern const int LSQLITE_IOERR_BEGIN_ATOMIC; 33 | extern const int LSQLITE_IOERR_COMMIT_ATOMIC; 34 | extern const int LSQLITE_IOERR_ROLLBACK_ATOMIC; 35 | extern const int LSQLITE_LOCKED_SHAREDCACHE; 36 | extern const int LSQLITE_LOCKED_VTAB; 37 | extern const int LSQLITE_BUSY_RECOVERY; 38 | extern const int LSQLITE_BUSY_SNAPSHOT; 39 | extern const int LSQLITE_CANTOPEN_NOTEMPDIR; 40 | extern const int LSQLITE_CANTOPEN_ISDIR; 41 | extern const int LSQLITE_CANTOPEN_FULLPATH; 42 | extern const int LSQLITE_CANTOPEN_CONVPATH; 43 | extern const int LSQLITE_CANTOPEN_DIRTYWAL; 44 | extern const int LSQLITE_CORRUPT_VTAB; 45 | extern const int LSQLITE_CORRUPT_SEQUENCE; 46 | extern const int LSQLITE_READONLY_RECOVERY; 47 | extern const int LSQLITE_READONLY_CANTLOCK; 48 | extern const int LSQLITE_READONLY_ROLLBACK; 49 | extern const int LSQLITE_READONLY_DBMOVED; 50 | extern const int LSQLITE_READONLY_CANTINIT; 51 | extern const int LSQLITE_READONLY_DIRECTORY; 52 | extern const int LSQLITE_ABORT_ROLLBACK; 53 | extern const int LSQLITE_CONSTRAINT_CHECK; 54 | extern const int LSQLITE_CONSTRAINT_COMMITHOOK; 55 | extern const int LSQLITE_CONSTRAINT_FOREIGNKEY; 56 | extern const int LSQLITE_CONSTRAINT_FUNCTION; 57 | extern const int LSQLITE_CONSTRAINT_NOTNULL; 58 | extern const int LSQLITE_CONSTRAINT_PRIMARYKEY; 59 | extern const int LSQLITE_CONSTRAINT_TRIGGER; 60 | extern const int LSQLITE_CONSTRAINT_UNIQUE; 61 | extern const int LSQLITE_CONSTRAINT_VTAB; 62 | extern const int LSQLITE_CONSTRAINT_ROWID; 63 | extern const int LSQLITE_NOTICE_RECOVER_WAL; 64 | extern const int LSQLITE_NOTICE_RECOVER_ROLLBACK; 65 | extern const int LSQLITE_WARNING_AUTOINDEX; 66 | extern const int LSQLITE_AUTH_USER; 67 | extern const int LSQLITE_OK_LOAD_PERMANENTLY; 68 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Checkpoint.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// WAL checkpoint modes used by `walCheckpoint(_:mode:frameCount:totalFrameCount:)` and auto-checkpointing. 5 | /// 6 | /// Related SQLite: `SQLITE_CHECKPOINT_PASSIVE`, `SQLITE_CHECKPOINT_FULL`, `SQLITE_CHECKPOINT_RESTART`, `SQLITE_CHECKPOINT_TRUNCATE` 7 | @frozen public struct CheckpointMode: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | public static let passive = Self(rawValue: SQLITE_CHECKPOINT_PASSIVE) 15 | public static let full = Self(rawValue: SQLITE_CHECKPOINT_FULL) 16 | public static let restart = Self(rawValue: SQLITE_CHECKPOINT_RESTART) 17 | public static let truncate = Self(rawValue: SQLITE_CHECKPOINT_TRUNCATE) 18 | 19 | /// Debug label for the checkpoint mode. 20 | /// 21 | /// Related SQLite: `SQLITE_CHECKPOINT_PASSIVE`, `SQLITE_CHECKPOINT_FULL`, `SQLITE_CHECKPOINT_RESTART`, `SQLITE_CHECKPOINT_TRUNCATE` 22 | public var debugDescription: String { 23 | switch self { 24 | case .passive: return "SQLITE_CHECKPOINT_PASSIVE" 25 | case .full: return "SQLITE_CHECKPOINT_FULL" 26 | case .restart: return "SQLITE_CHECKPOINT_RESTART" 27 | case .truncate: return "SQLITE_CHECKPOINT_TRUNCATE" 28 | default: return "CheckpointMode(rawValue: \(rawValue))" 29 | } 30 | } 31 | } 32 | 33 | /// Enables or disables automatic WAL checkpoints when the log reaches the given frame count. 34 | /// - Parameter pageInWALFileCount: Frame threshold; pass `<= 0` to turn off auto-checkpointing. 35 | /// - Returns: Result of `sqlite3_wal_autocheckpoint`. 36 | /// 37 | /// Related SQLite: `sqlite3_wal_autocheckpoint`, `sqlite3_wal_hook`, `PRAGMA wal_autocheckpoint`, `SQLITE_CHECKPOINT_PASSIVE` 38 | @inlinable public func autoWALCheckpoint(pageInWALFileCount: Int32) -> ResultCode { 39 | sqlite3_wal_autocheckpoint(rawValue, pageInWALFileCount).resultCode 40 | } 41 | 42 | /// Runs a WAL checkpoint on the named database using the requested mode. 43 | /// - Parameters: 44 | /// - databaseName: Target database name (e.g. `"main"`); empty or `NULL` applies to all. 45 | /// - mode: Checkpoint mode to use. 46 | /// - frameCount: Receives total frames in the WAL, or `-1` on failure. 47 | /// - totalFrameCount: Receives total checkpointed frames, or `-1` on failure. 48 | /// - Returns: Result of `sqlite3_wal_checkpoint_v2`. 49 | /// 50 | /// Related SQLite: `sqlite3_wal_checkpoint_v2`, `PRAGMA wal_checkpoint`, `SQLITE_CHECKPOINT_*` 51 | @inlinable public func walCheckpoint(_ databaseName: UnsafePointer, mode: CheckpointMode, frameCount: UnsafeMutablePointer?, totalFrameCount: UnsafeMutablePointer?) -> ResultCode { 52 | sqlite3_wal_checkpoint_v2(rawValue, databaseName, mode.rawValue, frameCount, totalFrameCount).resultCode 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/ExecTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import LSQLite 3 | 4 | class ExecTests: XCTestCase { 5 | 6 | var database: Database! 7 | 8 | override class func setUp() { 9 | super.setUp() 10 | _ = LSQLite.initialize() 11 | } 12 | 13 | override class func tearDown() { 14 | super.tearDown() 15 | _ = LSQLite.shutdown() 16 | } 17 | 18 | override func setUpWithError() throws { 19 | try super.setUpWithError() 20 | database = try { 21 | var database: Database? = nil 22 | guard Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create, .memory]) == .ok else { 23 | if let database = database { 24 | throw Error.result(database.lastErrorCode) 25 | } else { 26 | throw Error.unknown 27 | } 28 | } 29 | return database 30 | }() 31 | } 32 | 33 | override func tearDownWithError() throws { 34 | try super.tearDownWithError() 35 | guard database.close() == .ok else { 36 | throw Error.result(database.lastErrorCode) 37 | } 38 | } 39 | 40 | func testCreateDatabaseInsertRowRowsAndGetThemBack() { 41 | XCTAssertEqual(database.exec("CREATE TABLE t(num INTEGER, txt TEXT);"), .ok) 42 | XCTAssertEqual(database.exec("INSERT INTO t VALUES(1, 'text1');"), .ok) 43 | XCTAssertEqual(database.exec("INSERT INTO t VALUES(2, NULL);"), .ok) 44 | let counter = RefWrappedValue(0) 45 | let resultCode = database.exec("SELECT num, txt FROM t;", userData: Unmanaged.passUnretained(counter).toOpaque()) { userData, count, values, columns in 46 | let counter = Unmanaged>.fromOpaque(userData!).takeUnretainedValue() 47 | counter.value += 1 48 | let mappedValues: [String: String] = { 49 | var typedValues: [String?] = [] 50 | var typedColumns: [String] = [] 51 | for i in 0.. UnsafeMutableRawPointer? { 35 | sqlite3_malloc(size) 36 | } 37 | 38 | /// Allocates the requested number of bytes (64-bit length) using SQLite's allocator; returns nil on OOM. 39 | /// 40 | /// Related SQLite: `sqlite3_malloc64` 41 | @available(iOS 9.0, macOS 10.11, tvOS 9.0, watchOS 2.0, *) 42 | @inlinable public func malloc64(_ size: UInt64) -> UnsafeMutableRawPointer? { 43 | sqlite3_malloc64(size) 44 | } 45 | 46 | /// Resizes or allocates memory using SQLite's allocator; nil frees the buffer. 47 | /// 48 | /// Related SQLite: `sqlite3_realloc` 49 | @inlinable public func realloc(_ buffer: UnsafeMutableRawPointer?, _ size: Int32) -> UnsafeMutableRawPointer? { 50 | sqlite3_realloc(buffer, size) 51 | } 52 | 53 | /// Resizes or allocates memory using SQLite's allocator with a 64-bit length. 54 | /// 55 | /// Related SQLite: `sqlite3_realloc64` 56 | @available(iOS 9.0, macOS 10.11, tvOS 9.0, watchOS 2.0, *) 57 | @inlinable public func realloc64(_ buffer: UnsafeMutableRawPointer?, _ size: UInt64) -> UnsafeMutableRawPointer? { 58 | sqlite3_realloc64(buffer, size) 59 | } 60 | 61 | /// Frees memory allocated by SQLite's allocation routines (no-op on nil). 62 | /// 63 | /// Related SQLite: `sqlite3_free` 64 | @inlinable public func free(_ buffer: UnsafeMutableRawPointer?) { 65 | sqlite3_free(buffer) 66 | } 67 | 68 | /// Returns the size of an allocation obtained from SQLite's allocator. 69 | /// 70 | /// Related SQLite: `sqlite3_msize` 71 | @available(iOS 9.0, macOS 10.11, tvOS 9.0, watchOS 2.0, *) 72 | @inlinable public func msize(_ buffer: UnsafeMutableRawPointer?) -> UInt64 { 73 | sqlite3_msize(buffer) 74 | } 75 | 76 | /// Total bytes currently allocated by SQLite's memory subsystem. 77 | /// 78 | /// Related SQLite: `sqlite3_memory_used` 79 | @inlinable public var memoryUsed: Int64 { 80 | sqlite3_memory_used() 81 | } 82 | 83 | /// High-water mark of bytes allocated; use reset flag to optionally reset. 84 | /// 85 | /// Related SQLite: `sqlite3_memory_highwater` 86 | @inlinable public func memoryHighwater(_ resetFlag: MemoryHighWaterResetFlag) -> Int64 { 87 | sqlite3_memory_highwater(resetFlag.rawValue) 88 | } 89 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/StatementTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import LSQLite 3 | 4 | class StatementTests: XCTestCase { 5 | 6 | var database: Database! 7 | 8 | override class func setUp() { 9 | super.setUp() 10 | _ = LSQLite.initialize() 11 | } 12 | 13 | override class func tearDown() { 14 | super.tearDown() 15 | _ = LSQLite.shutdown() 16 | } 17 | 18 | override func setUpWithError() throws { 19 | try super.setUpWithError() 20 | database = try { 21 | var database: Database? = nil 22 | guard Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create, .memory]) == .ok, database != nil else { 23 | if let database = database { 24 | throw Error.result(database.lastErrorCode) 25 | } else { 26 | throw Error.unknown 27 | } 28 | } 29 | return database 30 | }() 31 | } 32 | 33 | override func tearDownWithError() throws { 34 | try super.tearDownWithError() 35 | guard database.close() == .ok else { 36 | throw Error.result(database.lastErrorCode) 37 | } 38 | } 39 | 40 | func testCreateDatabaseInsertRowRowsAndGetThemBack() throws { 41 | let createTableStatement: Statement = try { 42 | var statement: Statement? = nil 43 | let resultCode = Statement.prepare(&statement, sql: "CREATE TABLE t(num INTEGER, txt TEXT);", for: database) 44 | guard resultCode == .ok, statement != nil else { 45 | throw Error.result(resultCode) 46 | } 47 | return statement! 48 | }() 49 | XCTAssertEqual(createTableStatement.step(), .done) 50 | XCTAssertEqual(createTableStatement.finalize(), .ok) 51 | 52 | let insertStatement: Statement = try { 53 | var statement: Statement? = nil 54 | let resultCode = Statement.prepare(&statement, sql: "INSERT INTO t VALUES(?, ?);", for: database) 55 | guard resultCode == .ok, statement != nil else { 56 | throw Error.result(resultCode) 57 | } 58 | return statement! 59 | }() 60 | 61 | XCTAssertEqual(insertStatement.bindInt(1, at: 1), .ok) 62 | XCTAssertEqual(insertStatement.bindTransientText("text1", at: 2), .ok) 63 | XCTAssertEqual(insertStatement.step(), .done) 64 | XCTAssertEqual(insertStatement.clearBindings(), .ok) 65 | XCTAssertEqual(insertStatement.reset(), .ok) 66 | 67 | XCTAssertEqual(insertStatement.bindInt(2, at: 1), .ok) 68 | XCTAssertEqual(insertStatement.bindNull(at: 2), .ok) 69 | XCTAssertEqual(insertStatement.step(), .done) 70 | XCTAssertEqual(insertStatement.clearBindings(), .ok) 71 | XCTAssertEqual(insertStatement.reset(), .ok) 72 | 73 | XCTAssertEqual(insertStatement.finalize(), .ok) 74 | 75 | let selectStatement: Statement = try { 76 | var statement: Statement? = nil 77 | let resultCode = Statement.prepare(&statement, sql: "SELECT num, txt FROM t;", for: database) 78 | guard resultCode == .ok, statement != nil else { 79 | throw Error.result(resultCode) 80 | } 81 | return statement! 82 | }() 83 | 84 | XCTAssertEqual(selectStatement.step(), .row) 85 | XCTAssertEqual(selectStatement.columnInt(at: 0), 1) 86 | XCTAssertEqual(selectStatement.columnText(at: 1).map(String.init(cString:)), "text1") 87 | 88 | XCTAssertEqual(selectStatement.step(), .row) 89 | XCTAssertEqual(selectStatement.columnInt(at: 0), 2) 90 | XCTAssertEqual(selectStatement.columnText(at: 1).map(String.init(cString:)), nil) 91 | 92 | XCTAssertEqual(selectStatement.step(), .done) 93 | 94 | XCTAssertEqual(selectStatement.finalize(), .ok) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Column.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Number of columns in the result set. 5 | /// 6 | /// Related SQLite: `sqlite3_column_count` 7 | @inlinable public var columnCount: Int32 { 8 | sqlite3_column_count(rawValue) 9 | } 10 | 11 | /// Column name for the given 0-based index. 12 | /// 13 | /// Related SQLite: `sqlite3_column_name` 14 | @inlinable public func columnName(at index: Int32) -> UnsafePointer? { 15 | sqlite3_column_name(rawValue, index) 16 | } 17 | 18 | /// Database name for the column (or nil). 19 | /// 20 | /// Related SQLite: `sqlite3_column_database_name` 21 | @inlinable public func columnDatabaseName(at index: Int32) -> UnsafePointer? { 22 | sqlite3_column_database_name(rawValue, index) 23 | } 24 | 25 | /// Table name for the column (or nil). 26 | /// 27 | /// Related SQLite: `sqlite3_column_table_name` 28 | @inlinable public func columnTableName(at index: Int32) -> UnsafePointer? { 29 | sqlite3_column_table_name(rawValue, index) 30 | } 31 | 32 | /// Original column name before any AS alias (or nil). 33 | /// 34 | /// Related SQLite: `sqlite3_column_origin_name` 35 | @inlinable public func columnOriginName(at index: Int32) -> UnsafePointer? { 36 | sqlite3_column_origin_name(rawValue, index) 37 | } 38 | 39 | /// Declared type of the column (or nil). 40 | /// 41 | /// Related SQLite: `sqlite3_column_decltype` 42 | @inlinable public func columnDeclaredType(at index: Int32) -> UnsafePointer? { 43 | sqlite3_column_decltype(rawValue, index) 44 | } 45 | 46 | /// Number of columns with data in the current row (after stepping). 47 | /// 48 | /// Related SQLite: `sqlite3_data_count` 49 | @inlinable public var dataCount: Int32 { 50 | sqlite3_data_count(rawValue) 51 | } 52 | 53 | /// Blob pointer for the column value in the current row. 54 | /// 55 | /// Related SQLite: `sqlite3_column_blob`, `sqlite3_column_bytes` 56 | @inlinable public func columnBlob(at index: Int32) -> UnsafeRawPointer? { 57 | sqlite3_column_blob(rawValue, index) 58 | } 59 | 60 | /// Column value coerced to Double. 61 | /// 62 | /// Related SQLite: `sqlite3_column_double` 63 | @inlinable public func columnDouble(at index: Int32) -> Double { 64 | sqlite3_column_double(rawValue, index) 65 | } 66 | 67 | /// Column value coerced to 32-bit Int. 68 | /// 69 | /// Related SQLite: `sqlite3_column_int` 70 | @inlinable public func columnInt(at index: Int32) -> Int32 { 71 | sqlite3_column_int(rawValue, index) 72 | } 73 | 74 | /// Column value coerced to 64-bit Int. 75 | /// 76 | /// Related SQLite: `sqlite3_column_int64` 77 | @inlinable public func columnInt64(at index: Int32) -> sqlite3_int64 { 78 | sqlite3_column_int64(rawValue, index) 79 | } 80 | 81 | /// UTF-8 text pointer for the column value. 82 | /// 83 | /// Related SQLite: `sqlite3_column_text`, `sqlite3_column_bytes` 84 | @inlinable public func columnText(at index: Int32) -> UnsafePointer? { 85 | sqlite3_column_text(rawValue, index) 86 | } 87 | 88 | /// Byte length of the column value for text/blob in the current row. 89 | /// 90 | /// Related SQLite: `sqlite3_column_bytes` 91 | @inlinable public func columnBytes(at index: Int32) -> Int32 { 92 | sqlite3_column_bytes(rawValue, index) 93 | } 94 | 95 | /// Datatype code for the column value in the current row. 96 | /// 97 | /// Related SQLite: `sqlite3_column_type` 98 | @inlinable public func columnType(at index: Int32) -> Datatype { 99 | Datatype(rawValue: sqlite3_column_type(rawValue, index)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/LSQLite/Mutex/Mutex+Lifecycle.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Mutex { 4 | /// Mutex type constants used when allocating or requesting a static mutex. 5 | /// 6 | /// Related SQLite: `sqlite3_mutex_alloc`, `SQLITE_MUTEX_FAST`, `SQLITE_MUTEX_RECURSIVE`, `SQLITE_MUTEX_STATIC_*` 7 | @frozen public struct MutexType: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | public static let fast = Self(rawValue: SQLITE_MUTEX_FAST) 15 | public static let recursive = Self(rawValue: SQLITE_MUTEX_RECURSIVE) 16 | public static let staticMaster = Self(rawValue: SQLITE_MUTEX_STATIC_MASTER) 17 | public static let staticMem = Self(rawValue: SQLITE_MUTEX_STATIC_MEM) 18 | public static let staticMem2 = Self(rawValue: SQLITE_MUTEX_STATIC_MEM2) 19 | public static let staticOpen = Self(rawValue: SQLITE_MUTEX_STATIC_OPEN) 20 | public static let staticPrng = Self(rawValue: SQLITE_MUTEX_STATIC_PRNG) 21 | public static let staticLRU = Self(rawValue: SQLITE_MUTEX_STATIC_LRU) 22 | public static let staticLRU2 = Self(rawValue: SQLITE_MUTEX_STATIC_LRU2) 23 | public static let statucPMem = Self(rawValue: SQLITE_MUTEX_STATIC_PMEM) 24 | public static let staticApp1 = Self(rawValue: SQLITE_MUTEX_STATIC_APP1) 25 | public static let staticApp2 = Self(rawValue: SQLITE_MUTEX_STATIC_APP2) 26 | public static let staticApp3 = Self(rawValue: SQLITE_MUTEX_STATIC_APP3) 27 | public static let staticVFS1 = Self(rawValue: SQLITE_MUTEX_STATIC_VFS1) 28 | public static let staticVFS2 = Self(rawValue: SQLITE_MUTEX_STATIC_VFS2) 29 | public static let staticVFS3 = Self(rawValue: SQLITE_MUTEX_STATIC_VFS3) 30 | 31 | /// Debug label for the mutex type. 32 | /// 33 | /// Related SQLite: `sqlite3_mutex_alloc`, `SQLITE_MUTEX_FAST`, `SQLITE_MUTEX_RECURSIVE`, `SQLITE_MUTEX_STATIC_*` 34 | public var debugDescription: String { 35 | switch self { 36 | case .fast: return "SQLITE_MUTEX_FAST" 37 | case .recursive: return "SQLITE_MUTEX_RECURSIVE" 38 | case .staticMaster: return "SQLITE_MUTEX_STATIC_MASTER" 39 | case .staticMem: return "SQLITE_MUTEX_STATIC_MEM" 40 | case .staticMem2: return "SQLITE_MUTEX_STATIC_MEM2" 41 | case .staticOpen: return "SQLITE_MUTEX_STATIC_OPEN" 42 | case .staticPrng: return "SQLITE_MUTEX_STATIC_PRNG" 43 | case .staticLRU: return "SQLITE_MUTEX_STATIC_LRU" 44 | case .staticLRU2: return "SQLITE_MUTEX_STATIC_LRU2" 45 | case .statucPMem: return "SQLITE_MUTEX_STATIC_PMEM" 46 | case .staticApp1: return "SQLITE_MUTEX_STATIC_APP1" 47 | case .staticApp2: return "SQLITE_MUTEX_STATIC_APP2" 48 | case .staticApp3: return "SQLITE_MUTEX_STATIC_APP3" 49 | case .staticVFS1: return "SQLITE_MUTEX_STATIC_VFS1" 50 | case .staticVFS2: return "SQLITE_MUTEX_STATIC_VFS2" 51 | case .staticVFS3: return "SQLITE_MUTEX_STATIC_VFS3" 52 | default: return "MutexType(rawValue: \(rawValue))" 53 | } 54 | } 55 | } 56 | 57 | /// Allocates a mutex of the requested type (dynamic or static). 58 | /// - Parameter type: Mutex type to obtain. 59 | /// - Returns: A mutex handle or `nil` if allocation fails. 60 | /// 61 | /// Related SQLite: `sqlite3_mutex_alloc`, `SQLITE_MUTEX_FAST`, `SQLITE_MUTEX_RECURSIVE`, `SQLITE_MUTEX_STATIC_*` 62 | @inlinable public static func alloc(_ type: MutexType) -> Mutex? { 63 | sqlite3_mutex_alloc(type.rawValue).map(Mutex.init(rawValue:)) 64 | } 65 | 66 | /// Releases a dynamically allocated mutex. 67 | /// 68 | /// Related SQLite: `sqlite3_mutex_free` 69 | @inlinable public func free() { 70 | sqlite3_mutex_free(rawValue) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Trace.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 4 | extension Database { 5 | /// Trace callback invoked for subscribed events with event-specific payloads. 6 | /// 7 | /// Related SQLite: `sqlite3_trace_v2` 8 | public typealias TraceEventCallback = @convention(c) (_ traceEventCode: UInt32, _ userData: UnsafeMutableRawPointer?, _ p: UnsafeMutableRawPointer?, _ x: UnsafeMutableRawPointer?) -> Int32 9 | 10 | /// Return code expected from a trace callback. 11 | /// 12 | /// Related SQLite: `sqlite3_trace_v2` 13 | @frozen public struct TraceEventCallbackResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 14 | public let rawValue: Int32 15 | 16 | @inlinable public init(rawValue: Int32) { 17 | self.rawValue = rawValue 18 | } 19 | 20 | public static let ok = Self(rawValue: 0) 21 | 22 | /// Debug label for the trace callback result. 23 | /// 24 | /// Related SQLite: `sqlite3_trace_v2` 25 | public var debugDescription: String { 26 | switch self { 27 | case .ok: return "LSQLITE_OK" 28 | default: return "TraceEventCallbackResult(rawValue: \(rawValue))" 29 | } 30 | } 31 | } 32 | 33 | /// Event mask values used when registering `setTraceCallback(for:userData:callback:)`. 34 | /// 35 | /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` 36 | @frozen public struct TraceEventCode: Equatable, OptionSet, CustomDebugStringConvertible { 37 | public let rawValue: UInt32 38 | 39 | @inlinable public init(rawValue: UInt32) { 40 | self.rawValue = rawValue 41 | } 42 | 43 | /// Trace when a prepared statement starts executing (including trigger entries). 44 | /// 45 | /// Related SQLite: `SQLITE_TRACE_STMT`, `sqlite3_trace_v2`, `sqlite3_expanded_sql` 46 | public static let statement = Self(rawValue: UInt32(SQLITE_TRACE_STMT)) 47 | /// Trace profiling information when a statement finishes; X points to elapsed nanoseconds. 48 | /// 49 | /// Related SQLite: `SQLITE_TRACE_PROFILE`, `sqlite3_profile`, `sqlite3_trace_v2` 50 | public static let profile = Self(rawValue: UInt32(SQLITE_TRACE_PROFILE)) 51 | /// Trace each row produced by a prepared statement. 52 | /// 53 | /// Related SQLite: `SQLITE_TRACE_ROW`, `sqlite3_trace_v2` 54 | public static let row = Self(rawValue: UInt32(SQLITE_TRACE_ROW)) 55 | /// Trace when the database connection closes. 56 | /// 57 | /// Related SQLite: `SQLITE_TRACE_CLOSE`, `sqlite3_trace_v2` 58 | public static let close = Self(rawValue: UInt32(SQLITE_TRACE_CLOSE)) 59 | 60 | /// Debug label for the trace event code. 61 | /// 62 | /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` 63 | public var debugDescription: String { 64 | switch self { 65 | case .statement: return "SQLITE_TRACE_STMT" 66 | case .profile: return "SQLITE_TRACE_PROFILE" 67 | case .row: return "SQLITE_TRACE_ROW" 68 | case .close: return "SQLITE_TRACE_CLOSE" 69 | default: return "TraceEventCode(rawValue: \(rawValue))" 70 | } 71 | } 72 | } 73 | 74 | /// Registers or clears a trace callback for the specified events on this connection. 75 | /// - Parameters: 76 | /// - events: Bitmask of `TraceEventCode` values to observe. 77 | /// - userData: Custom context passed to the callback. 78 | /// - callback: Trace handler; `nil` disables tracing. 79 | /// - Returns: Result of `sqlite3_trace_v2`. 80 | /// 81 | /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` 82 | @inlinable public func setTraceCallback(for events: TraceEventCode, userData: UnsafeMutableRawPointer? = nil, callback: TraceEventCallback? = nil) -> ResultCode { 83 | sqlite3_trace_v2(rawValue, events.rawValue, callback, userData).resultCode 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Collation.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Comparator used by SQLite to order text for a custom collation. 5 | /// 6 | /// Related SQLite: `sqlite3_create_collation_v2`, `sqlite3_create_collation`, `sqlite3_create_collation16` 7 | public typealias CollationCompareHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ lhsLength: Int32, _ lhs: UnsafeRawPointer?, _ rhsLength: Int32, _ rhs: UnsafeRawPointer?) -> Int32 8 | 9 | /// Cleanup callback invoked when a custom collation is destroyed. 10 | /// 11 | /// Related SQLite: `sqlite3_create_collation_v2` 12 | public typealias CollationDestroyHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Void 13 | 14 | /// Callback used to lazily create missing collations on demand. 15 | /// 16 | /// Related SQLite: `sqlite3_collation_needed`, `sqlite3_collation_needed16` 17 | public typealias CollationNeededHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ collationFlag: Int32, _ name: UnsafePointer?) -> Void 18 | 19 | /// Text encoding and behavior flags for user-defined collations. 20 | /// 21 | /// Related SQLite: `sqlite3_create_collation_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` 22 | @frozen public struct CollationFlag: Equatable, RawRepresentable, CustomDebugStringConvertible { 23 | public let rawValue: Int32 24 | 25 | @inlinable public init(rawValue: Int32) { 26 | self.rawValue = rawValue 27 | } 28 | 29 | public static let utf8 = Self(rawValue: SQLITE_UTF8) 30 | public static let utf16le = Self(rawValue: SQLITE_UTF16LE) 31 | public static let utf16be = Self(rawValue: SQLITE_UTF16BE) 32 | public static let utf16 = Self(rawValue: SQLITE_UTF16) 33 | public static let utf16Aligned = Self(rawValue: SQLITE_UTF16_ALIGNED) 34 | 35 | public static let deterministic = Self(rawValue: SQLITE_DETERMINISTIC) 36 | 37 | /// Debug label for the collation flag. 38 | /// 39 | /// Related SQLite: `sqlite3_create_collation_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` 40 | public var debugDescription: String { 41 | switch self { 42 | case .utf8: return "SQLITE_UTF8" 43 | case .utf16le: return "SQLITE_UTF16LE" 44 | case .utf16be: return "SQLITE_UTF16BE" 45 | case .utf16: return "SQLITE_UTF16" 46 | case .utf16Aligned: return "SQLITE_UTF16_ALIGNED" 47 | default: return "FunctionFlag(rawValue: \(rawValue))" 48 | } 49 | } 50 | } 51 | 52 | /// Registers, replaces, or removes a collation on this connection using the provided comparator callbacks. 53 | /// - Parameters: 54 | /// - name: Collation name to create or replace (UTF-8). 55 | /// - flag: Text encoding and options for callback inputs. 56 | /// - userData: User context passed to `compareHandler` and `destroyHandler`. 57 | /// - compareHandler: Comparator returning negative/zero/positive for ordering; `nil` removes the collation. 58 | /// - destroyHandler: Optional cleanup invoked when the collation is dropped. 59 | /// - Returns: Result of `sqlite3_create_collation_v2`. 60 | /// 61 | /// Related SQLite: `sqlite3_create_collation_v2`, `sqlite3_create_collation`, `sqlite3_create_collation16`, `sqlite3_close`, `sqlite3_strnicmp` 62 | @inlinable public func createCollation(name: UnsafePointer, flag: CollationFlag, userData: UnsafeMutableRawPointer? = nil, compareHandler: CollationCompareHandler? = nil, destroyHandler: CollationDestroyHandler? = nil) -> ResultCode { 63 | sqlite3_create_collation_v2(rawValue, name, flag.rawValue, userData, compareHandler, destroyHandler).resultCode 64 | } 65 | 66 | /// Registers a callback invoked when SQLite encounters an unknown collation name so it can be created on demand. 67 | /// - Parameters: 68 | /// - userData: Custom context forwarded to the callback. 69 | /// - neededHandler: Handler describing the desired collation; pass `nil` to clear. 70 | /// - Returns: Result of `sqlite3_collation_needed`. 71 | /// 72 | /// Related SQLite: `sqlite3_collation_needed`, `sqlite3_collation_needed16`, `sqlite3_create_collation_v2` 73 | @inlinable public func collationNeeded(userData: UnsafeMutableRawPointer? = nil, neededHandler: CollationNeededHandler? = nil) -> ResultCode { 74 | sqlite3_collation_needed(rawValue, userData, neededHandler).resultCode 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Open.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// SQLite filename wrapper used when opening a connection. 5 | /// 6 | /// To construct a `file:` URI filename, use `Database.FileName.uri(...)` and open with `.uri` in the flags. 7 | /// 8 | /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_temp_directory`, `SQLITE_OPEN_URI` 9 | @frozen public struct FileName: RawRepresentable, Sendable { 10 | public let rawValue: String 11 | 12 | /// Creates a filename wrapper from a Swift string. 13 | /// 14 | /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2` 15 | @inlinable public init(rawValue: String) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | /// Filename for an in-memory database. 20 | /// 21 | /// Related SQLite: `":memory:"`, `sqlite3_open`, `sqlite3_open_v2` 22 | public static let memory = Self(rawValue: ":memory:") 23 | 24 | /// Filename for a temporary on-disk database. 25 | /// 26 | /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_temp_directory` 27 | public static let temporary = Self(rawValue: "") 28 | } 29 | 30 | /// Flags passed to `open(_:at:withOpenFlags:)` and custom VFS xOpen calls. 31 | /// 32 | /// Related SQLite: `sqlite3_open_v2`, `sqlite3_vfs.xOpen`, `SQLITE_OPEN_*` 33 | @frozen public struct OpenFlag: OptionSet { 34 | public let rawValue: Int32 35 | 36 | @inlinable public init(rawValue: Int32) { 37 | self.rawValue = rawValue 38 | } 39 | 40 | public static let readonly = Self(rawValue: SQLITE_OPEN_READONLY) 41 | public static let readwrite = Self(rawValue: SQLITE_OPEN_READWRITE) 42 | public static let create = Self(rawValue: SQLITE_OPEN_CREATE) 43 | public static let deleteOnClose = Self(rawValue: SQLITE_OPEN_DELETEONCLOSE) 44 | public static let exclusive = Self(rawValue: SQLITE_OPEN_EXCLUSIVE) 45 | public static let autoproxy = Self(rawValue: SQLITE_OPEN_AUTOPROXY) 46 | public static let uri = Self(rawValue: SQLITE_OPEN_URI) 47 | public static let memory = Self(rawValue: SQLITE_OPEN_MEMORY) 48 | public static let mainDB = Self(rawValue: SQLITE_OPEN_MAIN_DB) 49 | public static let tempDB = Self(rawValue: SQLITE_OPEN_TEMP_DB) 50 | public static let transientDB = Self(rawValue: SQLITE_OPEN_TRANSIENT_DB) 51 | public static let mainJournal = Self(rawValue: SQLITE_OPEN_MAIN_JOURNAL) 52 | public static let tempJournal = Self(rawValue: SQLITE_OPEN_TEMP_JOURNAL) 53 | public static let subjournal = Self(rawValue: SQLITE_OPEN_SUBJOURNAL) 54 | public static let masterJournal = Self(rawValue: SQLITE_OPEN_MASTER_JOURNAL) 55 | public static let noMutex = Self(rawValue: SQLITE_OPEN_NOMUTEX) 56 | public static let fullMutex = Self(rawValue: SQLITE_OPEN_FULLMUTEX) 57 | public static let sharedCache = Self(rawValue: SQLITE_OPEN_SHAREDCACHE) 58 | public static let privateCache = Self(rawValue: SQLITE_OPEN_PRIVATECACHE) 59 | public static let wal = Self(rawValue: SQLITE_OPEN_WAL) 60 | #if canImport(Darwin) 61 | public static let fileProtectionComplete = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_COMPLETE) 62 | public static let fileProtectionCompleteUnlessOpen = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN) 63 | public static let fileProtectionCompleteUntilFirstUserAuthentication = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION) 64 | public static let fileProtectionNone = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_NONE) 65 | public static let fileProtectionMask = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_MASK) 66 | #endif 67 | } 68 | 69 | /// Opens a database connection at the given filename using the supplied flags. 70 | /// - Parameters: 71 | /// - database: Output connection handle when opening succeeds. 72 | /// - filename: Target database path or helper filename like `.memory`. 73 | /// - openFlag: Flags controlling open mode and options. 74 | /// - Returns: Result code from `sqlite3_open_v2`. 75 | /// 76 | /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `SQLITE_OPEN_*`, `sqlite3_temp_directory` 77 | @inlinable public static func open(_ database: inout Database?, at filename: FileName, withOpenFlags openFlag: OpenFlag) -> ResultCode { 78 | var databasePointer: OpaquePointer? = nil 79 | let resultCode = sqlite3_open_v2(filename.rawValue, &databasePointer, openFlag.rawValue, nil).resultCode 80 | database = databasePointer.map(Database.init(rawValue:)) 81 | return resultCode 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/MissedSwiftSQLite/ResultCode.c: -------------------------------------------------------------------------------- 1 | #include "ResultCode.h" 2 | #import 3 | 4 | const int LSQLITE_ERROR_MISSING_COLLSEQ = SQLITE_ERROR_MISSING_COLLSEQ; 5 | const int LSQLITE_ERROR_RETRY = SQLITE_ERROR_RETRY; 6 | const int LSQLITE_ERROR_SNAPSHOT = SQLITE_ERROR_SNAPSHOT; 7 | const int LSQLITE_IOERR_READ = SQLITE_IOERR_READ; 8 | const int LSQLITE_IOERR_SHORT_READ = SQLITE_IOERR_SHORT_READ; 9 | const int LSQLITE_IOERR_WRITE = SQLITE_IOERR_WRITE; 10 | const int LSQLITE_IOERR_FSYNC = SQLITE_IOERR_FSYNC; 11 | const int LSQLITE_IOERR_DIR_FSYNC = SQLITE_IOERR_DIR_FSYNC; 12 | const int LSQLITE_IOERR_TRUNCATE = SQLITE_IOERR_TRUNCATE; 13 | const int LSQLITE_IOERR_FSTAT = SQLITE_IOERR_FSTAT; 14 | const int LSQLITE_IOERR_UNLOCK = SQLITE_IOERR_UNLOCK; 15 | const int LSQLITE_IOERR_RDLOCK = SQLITE_IOERR_RDLOCK; 16 | const int LSQLITE_IOERR_DELETE = SQLITE_IOERR_DELETE; 17 | const int LSQLITE_IOERR_BLOCKED = SQLITE_IOERR_BLOCKED; 18 | const int LSQLITE_IOERR_NOMEM = SQLITE_IOERR_NOMEM; 19 | const int LSQLITE_IOERR_ACCESS = SQLITE_IOERR_ACCESS; 20 | const int LSQLITE_IOERR_CHECKRESERVEDLOCK = SQLITE_IOERR_CHECKRESERVEDLOCK; 21 | const int LSQLITE_IOERR_LOCK = SQLITE_IOERR_LOCK; 22 | const int LSQLITE_IOERR_CLOSE = SQLITE_IOERR_CLOSE; 23 | const int LSQLITE_IOERR_DIR_CLOSE = SQLITE_IOERR_DIR_CLOSE; 24 | const int LSQLITE_IOERR_SHMOPEN = SQLITE_IOERR_SHMOPEN; 25 | const int LSQLITE_IOERR_SHMSIZE = SQLITE_IOERR_SHMSIZE; 26 | const int LSQLITE_IOERR_SHMLOCK = SQLITE_IOERR_SHMLOCK; 27 | const int LSQLITE_IOERR_SHMMAP = SQLITE_IOERR_SHMMAP; 28 | const int LSQLITE_IOERR_SEEK = SQLITE_IOERR_SEEK; 29 | const int LSQLITE_IOERR_DELETE_NOENT = SQLITE_IOERR_DELETE_NOENT; 30 | const int LSQLITE_IOERR_MMAP = SQLITE_IOERR_MMAP; 31 | const int LSQLITE_IOERR_GETTEMPPATH = SQLITE_IOERR_GETTEMPPATH; 32 | const int LSQLITE_IOERR_CONVPATH = SQLITE_IOERR_CONVPATH; 33 | const int LSQLITE_IOERR_VNODE = SQLITE_IOERR_VNODE; 34 | const int LSQLITE_IOERR_AUTH = SQLITE_IOERR_AUTH; 35 | const int LSQLITE_IOERR_BEGIN_ATOMIC = SQLITE_IOERR_BEGIN_ATOMIC; 36 | const int LSQLITE_IOERR_COMMIT_ATOMIC = SQLITE_IOERR_COMMIT_ATOMIC; 37 | const int LSQLITE_IOERR_ROLLBACK_ATOMIC = SQLITE_IOERR_ROLLBACK_ATOMIC; 38 | const int LSQLITE_LOCKED_SHAREDCACHE = SQLITE_LOCKED_SHAREDCACHE; 39 | const int LSQLITE_LOCKED_VTAB = SQLITE_LOCKED_VTAB; 40 | const int LSQLITE_BUSY_RECOVERY = SQLITE_BUSY_RECOVERY; 41 | const int LSQLITE_BUSY_SNAPSHOT = SQLITE_BUSY_SNAPSHOT; 42 | const int LSQLITE_CANTOPEN_NOTEMPDIR = SQLITE_CANTOPEN_NOTEMPDIR; 43 | const int LSQLITE_CANTOPEN_ISDIR = SQLITE_CANTOPEN_ISDIR; 44 | const int LSQLITE_CANTOPEN_FULLPATH = SQLITE_CANTOPEN_FULLPATH; 45 | const int LSQLITE_CANTOPEN_CONVPATH = SQLITE_CANTOPEN_CONVPATH; 46 | const int LSQLITE_CANTOPEN_DIRTYWAL = SQLITE_CANTOPEN_DIRTYWAL; 47 | const int LSQLITE_CORRUPT_VTAB = SQLITE_CORRUPT_VTAB; 48 | const int LSQLITE_CORRUPT_SEQUENCE = SQLITE_CORRUPT_SEQUENCE; 49 | const int LSQLITE_READONLY_RECOVERY = SQLITE_READONLY_RECOVERY; 50 | const int LSQLITE_READONLY_CANTLOCK = SQLITE_READONLY_CANTLOCK; 51 | const int LSQLITE_READONLY_ROLLBACK = SQLITE_READONLY_ROLLBACK; 52 | const int LSQLITE_READONLY_DBMOVED = SQLITE_READONLY_DBMOVED; 53 | const int LSQLITE_READONLY_CANTINIT = SQLITE_READONLY_CANTINIT; 54 | const int LSQLITE_READONLY_DIRECTORY = SQLITE_READONLY_DIRECTORY; 55 | const int LSQLITE_ABORT_ROLLBACK = SQLITE_ABORT_ROLLBACK; 56 | const int LSQLITE_CONSTRAINT_CHECK = SQLITE_CONSTRAINT_CHECK; 57 | const int LSQLITE_CONSTRAINT_COMMITHOOK = SQLITE_CONSTRAINT_COMMITHOOK; 58 | const int LSQLITE_CONSTRAINT_FOREIGNKEY = SQLITE_CONSTRAINT_FOREIGNKEY; 59 | const int LSQLITE_CONSTRAINT_FUNCTION = SQLITE_CONSTRAINT_FUNCTION; 60 | const int LSQLITE_CONSTRAINT_NOTNULL = SQLITE_CONSTRAINT_NOTNULL; 61 | const int LSQLITE_CONSTRAINT_PRIMARYKEY = SQLITE_CONSTRAINT_PRIMARYKEY; 62 | const int LSQLITE_CONSTRAINT_TRIGGER = SQLITE_CONSTRAINT_TRIGGER; 63 | const int LSQLITE_CONSTRAINT_UNIQUE = SQLITE_CONSTRAINT_UNIQUE; 64 | const int LSQLITE_CONSTRAINT_VTAB = SQLITE_CONSTRAINT_VTAB; 65 | const int LSQLITE_CONSTRAINT_ROWID = SQLITE_CONSTRAINT_ROWID; 66 | const int LSQLITE_NOTICE_RECOVER_WAL = SQLITE_NOTICE_RECOVER_WAL; 67 | const int LSQLITE_NOTICE_RECOVER_ROLLBACK = SQLITE_NOTICE_RECOVER_ROLLBACK; 68 | const int LSQLITE_WARNING_AUTOINDEX = SQLITE_WARNING_AUTOINDEX; 69 | const int LSQLITE_AUTH_USER = SQLITE_AUTH_USER; 70 | const int LSQLITE_OK_LOAD_PERMANENTLY = SQLITE_OK_LOAD_PERMANENTLY; 71 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Limit.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Runtime limit categories used with `Database.limit(for:)` and `Database.setLimit(_:for:)`. 5 | /// 6 | /// Related SQLite: `sqlite3_limit`, `SQLITE_LIMIT_*` 7 | @frozen public struct LimitCategory: Equatable, RawRepresentable, CustomDebugStringConvertible { 8 | public let rawValue: Int32 9 | 10 | @inlinable public init(rawValue: Int32) { 11 | self.rawValue = rawValue 12 | } 13 | 14 | /// Maximum size of any string, BLOB, or table row in bytes. 15 | /// 16 | /// Related SQLite: `SQLITE_LIMIT_LENGTH` 17 | public static let length = Self(rawValue: SQLITE_LIMIT_LENGTH) 18 | /// Maximum length of an SQL statement in bytes. 19 | /// 20 | /// Related SQLite: `SQLITE_LIMIT_SQL_LENGTH` 21 | public static let sqlLength = Self(rawValue: SQLITE_LIMIT_SQL_LENGTH) 22 | /// Maximum number of columns in a table definition, result set, index, ORDER BY, or GROUP BY. 23 | /// 24 | /// Related SQLite: `SQLITE_LIMIT_COLUMN` 25 | public static let column = Self(rawValue: SQLITE_LIMIT_COLUMN) 26 | /// Maximum expression parse-tree depth. 27 | /// 28 | /// Related SQLite: `SQLITE_LIMIT_EXPR_DEPTH` 29 | public static let exprDepth = Self(rawValue: SQLITE_LIMIT_EXPR_DEPTH) 30 | /// Maximum terms in a compound SELECT. 31 | /// 32 | /// Related SQLite: `SQLITE_LIMIT_COMPOUND_SELECT` 33 | public static let compoundSelect = Self(rawValue: SQLITE_LIMIT_COMPOUND_SELECT) 34 | /// Maximum opcodes in a single prepared statement VM program. 35 | /// 36 | /// Related SQLite: `SQLITE_LIMIT_VDBE_OP` 37 | public static let vdbeOp = Self(rawValue: SQLITE_LIMIT_VDBE_OP) 38 | /// Maximum number of arguments on a function. 39 | /// 40 | /// Related SQLite: `SQLITE_LIMIT_FUNCTION_ARG` 41 | public static let functionArg = Self(rawValue: SQLITE_LIMIT_FUNCTION_ARG) 42 | /// Maximum number of attached databases. 43 | /// 44 | /// Related SQLite: `SQLITE_LIMIT_ATTACHED` 45 | public static let attached = Self(rawValue: SQLITE_LIMIT_ATTACHED) 46 | /// Maximum pattern length for LIKE or GLOB. 47 | /// 48 | /// Related SQLite: `SQLITE_LIMIT_LIKE_PATTERN_LENGTH` 49 | public static let likePatternLength = Self(rawValue: SQLITE_LIMIT_LIKE_PATTERN_LENGTH) 50 | /// Maximum parameter index in a statement. 51 | /// 52 | /// Related SQLite: `SQLITE_LIMIT_VARIABLE_NUMBER` 53 | public static let variableNumber = Self(rawValue: SQLITE_LIMIT_VARIABLE_NUMBER) 54 | /// Maximum trigger recursion depth. 55 | /// 56 | /// Related SQLite: `SQLITE_LIMIT_TRIGGER_DEPTH` 57 | public static let triggerDepth = Self(rawValue: SQLITE_LIMIT_TRIGGER_DEPTH) 58 | /// Maximum auxiliary worker threads a prepared statement may start. 59 | /// 60 | /// Related SQLite: `SQLITE_LIMIT_WORKER_THREADS` 61 | public static let workerThreads = Self(rawValue: SQLITE_LIMIT_WORKER_THREADS) 62 | 63 | /// Debug label for the limit category. 64 | /// 65 | /// Related SQLite: `SQLITE_LIMIT_*` 66 | public var debugDescription: String { 67 | switch self { 68 | case .length: return "SQLITE_LIMIT_LENGTH" 69 | case .sqlLength: return "SQLITE_LIMIT_SQL_LENGTH)" 70 | case .column: return "SQLITE_LIMIT_COLUMN)" 71 | case .exprDepth: return "SQLITE_LIMIT_EXPR_DEPTH)" 72 | case .compoundSelect: return "SQLITE_LIMIT_COMPOUND_SELECT)" 73 | case .vdbeOp: return "SQLITE_LIMIT_VDBE_OP)" 74 | case .functionArg: return "SQLITE_LIMIT_FUNCTION_ARG)" 75 | case .attached: return "SQLITE_LIMIT_ATTACHED)" 76 | case .likePatternLength: return "SQLITE_LIMIT_LIKE_PATTERN_LENGTH)" 77 | case .variableNumber: return "SQLITE_LIMIT_VARIABLE_NUMBER)" 78 | case .triggerDepth: return "SQLITE_LIMIT_TRIGGER_DEPTH)" 79 | case .workerThreads: return "SQLITE_LIMIT_WORKER_THREADS)" 80 | default: return "LimitCategory(rawValue: \(rawValue))" 81 | } 82 | } 83 | } 84 | 85 | /// Reads the current run-time limit for the given category on this connection. 86 | /// - Parameter category: Limit to query. 87 | /// - Returns: Current value for the limit. 88 | /// 89 | /// Related SQLite: `sqlite3_limit`, `SQLITE_LIMIT_*` 90 | @inlinable public func limit(for category: LimitCategory) -> Int32 { 91 | sqlite3_limit(rawValue, category.rawValue, -1) 92 | } 93 | 94 | /// Sets a run-time limit for this connection and returns the previous value. 95 | /// - Parameters: 96 | /// - limit: New limit value; pass a negative number to leave the limit unchanged. 97 | /// - category: Limit to update. 98 | /// - Returns: Prior value of the limit. 99 | /// 100 | /// Related SQLite: `sqlite3_limit`, `SQLITE_LIMIT_*` 101 | @discardableResult @inlinable public func setLimit(_ limit: Int32, for category: LimitCategory) -> Int32 { 102 | sqlite3_limit(rawValue, category.rawValue, limit) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/LSQLiteTests/FunctionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import LSQLite 3 | 4 | class FunctionTests: XCTestCase { 5 | 6 | var database: Database! 7 | 8 | override class func setUp() { 9 | super.setUp() 10 | _ = LSQLite.initialize() 11 | } 12 | 13 | override class func tearDown() { 14 | super.tearDown() 15 | _ = LSQLite.shutdown() 16 | } 17 | 18 | override func setUpWithError() throws { 19 | try super.setUpWithError() 20 | database = try { 21 | var database: Database? = nil 22 | guard Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create, .memory]) == .ok else { 23 | if let database = database { 24 | throw Error.result(database.lastErrorCode) 25 | } else { 26 | throw Error.unknown 27 | } 28 | } 29 | return database 30 | }() 31 | let myFiveResultCode = database.createFunction(name: "my_five", argumentCount: 0, flags: .utf8, userData: nil, funcHandler: { context, _, _ in 32 | let context = Context(rawValue: context!) 33 | context.resultInt(5) 34 | }) 35 | XCTAssertEqual(myFiveResultCode, .ok) 36 | let myCountResultCode = database.createFunction(name: "my_count", argumentCount: 0, flags: .utf8, userData: nil, stepHandler: { context, _, _ in 37 | let context = Context(rawValue: context!) 38 | let aggregatePointer = context.aggregateContext(size: Int32(MemoryLayout.stride)) 39 | guard let valuePointer = aggregatePointer?.assumingMemoryBound(to: Int32.self) else { 40 | context.resultNoMemoryError() 41 | return 42 | } 43 | valuePointer.pointee += 1 44 | }, finalHandler: { context in 45 | let context = Context(rawValue: context!) 46 | let value = context.aggregateContext(size: Int32(MemoryLayout.stride))!.assumingMemoryBound(to: Int32.self).pointee 47 | context.resultInt(value) 48 | }) 49 | XCTAssertEqual(myCountResultCode, .ok) 50 | } 51 | 52 | override func tearDownWithError() throws { 53 | try super.tearDownWithError() 54 | guard database.close() == .ok else { 55 | throw Error.result(database.lastErrorCode) 56 | } 57 | } 58 | 59 | func testCreateDatabaseInsertRowRowsAndGetThemBack() throws { 60 | let createTableStatement: Statement = try { 61 | var statement: Statement? = nil 62 | let resultCode = Statement.prepare(&statement, sql: "CREATE TABLE t(num INTEGER);", for: database) 63 | guard resultCode == .ok, statement != nil else { 64 | throw Error.result(resultCode) 65 | } 66 | return statement! 67 | }() 68 | XCTAssertEqual(createTableStatement.step(), .done) 69 | XCTAssertEqual(createTableStatement.finalize(), .ok) 70 | 71 | let insertStatement: Statement = try { 72 | var statement: Statement? = nil 73 | let resultCode = Statement.prepare(&statement, sql: "INSERT INTO t VALUES(?);", for: database) 74 | guard resultCode == .ok, statement != nil else { 75 | throw Error.result(resultCode) 76 | } 77 | return statement! 78 | }() 79 | 80 | XCTAssertEqual(insertStatement.bindInt(1, at: 1), .ok) 81 | XCTAssertEqual(insertStatement.step(), .done) 82 | XCTAssertEqual(insertStatement.clearBindings(), .ok) 83 | XCTAssertEqual(insertStatement.reset(), .ok) 84 | 85 | XCTAssertEqual(insertStatement.bindInt(2, at: 1), .ok) 86 | XCTAssertEqual(insertStatement.step(), .done) 87 | XCTAssertEqual(insertStatement.clearBindings(), .ok) 88 | XCTAssertEqual(insertStatement.reset(), .ok) 89 | 90 | XCTAssertEqual(insertStatement.finalize(), .ok) 91 | 92 | let selectStatement: Statement = try { 93 | var statement: Statement? = nil 94 | let resultCode = Statement.prepare(&statement, sql: "SELECT num, my_five() as nop FROM t;", for: database) 95 | guard resultCode == .ok, statement != nil else { 96 | throw Error.result(resultCode) 97 | } 98 | return statement! 99 | }() 100 | 101 | XCTAssertEqual(selectStatement.step(), .row) 102 | XCTAssertEqual(selectStatement.columnInt(at: 0), 1) 103 | XCTAssertEqual(selectStatement.columnInt(at: 1), 5) 104 | 105 | XCTAssertEqual(selectStatement.step(), .row) 106 | XCTAssertEqual(selectStatement.columnInt(at: 0), 2) 107 | XCTAssertEqual(selectStatement.columnInt(at: 1), 5) 108 | 109 | XCTAssertEqual(selectStatement.step(), .done) 110 | 111 | XCTAssertEqual(selectStatement.finalize(), .ok) 112 | 113 | let countStatement: Statement = try { 114 | var statement: Statement? = nil 115 | let resultCode = Statement.prepare(&statement, sql: "SELECT my_count() as count FROM t;", for: database) 116 | guard resultCode == .ok, statement != nil else { 117 | throw Error.result(resultCode) 118 | } 119 | return statement! 120 | }() 121 | 122 | XCTAssertEqual(countStatement.step(), .row) 123 | XCTAssertEqual(countStatement.columnInt(at: 0), 2) 124 | 125 | XCTAssertEqual(countStatement.step(), .done) 126 | 127 | XCTAssertEqual(countStatement.finalize(), .ok) 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Sources/LSQLite/Statement/Statement+Bind.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Statement { 4 | /// Destructor invoked when SQLite frees a bound blob buffer. 5 | /// 6 | /// Related SQLite: `sqlite3_bind_blob`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 7 | public typealias BindBlobDestructor = @convention(c) (_ blob: UnsafeMutableRawPointer?) -> Void 8 | 9 | /// Destructor invoked when SQLite frees a bound text buffer. 10 | /// 11 | /// Related SQLite: `sqlite3_bind_text`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 12 | public typealias BindTextDestructor = @convention(c) (_ text: UnsafeMutableRawPointer?) -> Void 13 | 14 | /// Binds raw blob data to the 1-based parameter index with a destructor. 15 | /// 16 | /// Related SQLite: `sqlite3_bind_blob`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 17 | @inlinable public func bindBlob(_ blob: UnsafeRawPointer, length: Int32, at index: Int32, destructor: BindBlobDestructor) -> ResultCode { 18 | sqlite3_bind_blob(rawValue, index, blob, length, destructor).resultCode 19 | } 20 | 21 | /// Binds blob data by copying it immediately (transient) to the parameter index. 22 | /// 23 | /// Related SQLite: `sqlite3_bind_blob`, `SQLITE_TRANSIENT` 24 | @inlinable public func bindTransientBlob(_ blob: UnsafeRawPointer, length: Int32, at index: Int32) -> ResultCode { 25 | lsqlite3_bind_transient_blob(rawValue, index, blob, length).resultCode 26 | } 27 | 28 | /// Binds blob data that remains valid for the statement lifetime (static) to the parameter index. 29 | /// 30 | /// Related SQLite: `sqlite3_bind_blob`, `SQLITE_STATIC` 31 | @inlinable public func bindStaticBlob(_ blob: UnsafeRawPointer, length: Int32, at index: Int32) -> ResultCode { 32 | lsqlite3_bind_static_blob(rawValue, index, blob, length).resultCode 33 | } 34 | 35 | /// Binds a Double value to the parameter index. 36 | /// 37 | /// Related SQLite: `sqlite3_bind_double` 38 | @inlinable public func bindDouble(_ double: Double, at index: Int32) -> ResultCode { 39 | sqlite3_bind_double(rawValue, index, double).resultCode 40 | } 41 | 42 | /// Binds a 32-bit integer to the parameter index. 43 | /// 44 | /// Related SQLite: `sqlite3_bind_int` 45 | @inlinable public func bindInt(_ int: Int32, at index: Int32) -> ResultCode { 46 | sqlite3_bind_int(rawValue, index, int).resultCode 47 | } 48 | 49 | /// Binds a 64-bit integer to the parameter index. 50 | /// 51 | /// Related SQLite: `sqlite3_bind_int64` 52 | @inlinable public func bindInt64(_ int: sqlite3_int64, at index: Int32) -> ResultCode { 53 | sqlite3_bind_int64(rawValue, index, int).resultCode 54 | } 55 | 56 | /// Binds a NULL value to the parameter index. 57 | /// 58 | /// Related SQLite: `sqlite3_bind_null` 59 | @inlinable public func bindNull(at index: Int32) -> ResultCode { 60 | sqlite3_bind_null(rawValue, index).resultCode 61 | } 62 | 63 | /// Binds UTF-8 text to the parameter index with a destructor; length defaults to the full string. 64 | /// 65 | /// Related SQLite: `sqlite3_bind_text`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 66 | @inlinable public func bindText(_ text: UnsafePointer, length: Int32 = -1, at index: Int32, destructor: BindTextDestructor) -> ResultCode { 67 | sqlite3_bind_text(rawValue, index, text, length, destructor).resultCode 68 | } 69 | 70 | /// Binds UTF-8 text by copying it immediately (transient) to the parameter index. 71 | /// 72 | /// Related SQLite: `sqlite3_bind_text`, `SQLITE_TRANSIENT` 73 | @inlinable public func bindTransientText(_ text: UnsafePointer, length: Int32 = -1, at index: Int32) -> ResultCode { 74 | sqlite3_bind_transient_text(rawValue, index, text, length).resultCode 75 | } 76 | 77 | /// Binds UTF-8 text that remains valid for the statement lifetime (static) to the parameter index. 78 | /// 79 | /// Related SQLite: `sqlite3_bind_text`, `SQLITE_STATIC` 80 | @inlinable public func bindStaticText(_ text: UnsafePointer, length: Int32 = -1, at index: Int32) -> ResultCode { 81 | sqlite3_bind_static_text(rawValue, index, text, length).resultCode 82 | } 83 | 84 | /// Binds a zero-filled BLOB of the given length to the parameter index. 85 | /// 86 | /// Related SQLite: `sqlite3_bind_zeroblob` 87 | @inlinable public func bindZeroBlob(length: Int32, at index: Int32) -> ResultCode { 88 | sqlite3_bind_zeroblob(rawValue, index, length).resultCode 89 | } 90 | 91 | /// Number of parameters expected by this prepared statement. 92 | /// 93 | /// Related SQLite: `sqlite3_bind_parameter_count` 94 | @inlinable public var bindingCount: Int32 { 95 | sqlite3_bind_parameter_count(rawValue) 96 | } 97 | 98 | /// Returns the parameter name for a 1-based index, or nil if positional. 99 | /// 100 | /// Related SQLite: `sqlite3_bind_parameter_name` 101 | @inlinable public func bindingName(at index: Int32) -> UnsafePointer? { 102 | sqlite3_bind_parameter_name(rawValue, index) 103 | } 104 | 105 | /// Returns the 1-based index for a named parameter, or 0 if not found. 106 | /// 107 | /// Related SQLite: `sqlite3_bind_parameter_index` 108 | @inlinable public func bindingIndex(with name: UnsafePointer) -> Int32 { 109 | sqlite3_bind_parameter_index(rawValue, name) 110 | } 111 | 112 | /// Resets all bound parameters on the statement back to NULL. 113 | /// 114 | /// Related SQLite: `sqlite3_clear_bindings` 115 | @inlinable public func clearBindings() -> ResultCode { 116 | sqlite3_clear_bindings(rawValue).resultCode 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/LSQLite/Context/Context+Result.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Context { 4 | /// Destructor invoked when SQLite frees a blob result buffer. 5 | /// 6 | /// Related SQLite: `sqlite3_result_blob`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 7 | public typealias ResultBlobDestructor = @convention(c) (_ blob: UnsafeMutableRawPointer?) -> Void 8 | 9 | /// Destructor invoked when SQLite frees a text result buffer. 10 | /// 11 | /// Related SQLite: `sqlite3_result_text`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 12 | public typealias ResultTextDestructor = @convention(c) (_ blob: UnsafeMutableRawPointer?) -> Void 13 | 14 | /// Sets the function result to the given blob with optional destructor. 15 | /// 16 | /// Related SQLite: `sqlite3_result_blob`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 17 | @inlinable public func resultBlob(_ blob: UnsafeRawPointer, length: Int32, destructor: ResultBlobDestructor? = nil) { 18 | sqlite3_result_blob(rawValue, blob, length, destructor) 19 | } 20 | 21 | /// Sets the result blob by copying the bytes immediately (transient). 22 | /// 23 | /// Related SQLite: `sqlite3_result_blob`, `SQLITE_TRANSIENT` 24 | @inlinable public func resultTransientBlob(_ blob: UnsafeRawPointer, length: Int32) { 25 | lsqlite3_result_transient_blob(rawValue, blob, length) 26 | } 27 | 28 | /// Sets the result blob that remains valid for SQLite to use (static). 29 | /// 30 | /// Related SQLite: `sqlite3_result_blob`, `SQLITE_STATIC` 31 | @inlinable public func resultStaticBlob(_ blob: UnsafeRawPointer, length: Int32) { 32 | lsqlite3_result_static_blob(rawValue, blob, length) 33 | } 34 | 35 | /// Sets the function result to a Double. 36 | /// 37 | /// Related SQLite: `sqlite3_result_double` 38 | @inlinable public func resultDouble(_ double: Double) { 39 | sqlite3_result_double(rawValue, double) 40 | } 41 | 42 | /// Sets an error result with the provided UTF-8 message. 43 | /// 44 | /// Related SQLite: `sqlite3_result_error` 45 | @inlinable public func resultError(_ message: UnsafePointer, length: Int32) { 46 | sqlite3_result_error(rawValue, message, length) 47 | } 48 | 49 | /// Sets an error result indicating the value is too large. 50 | /// 51 | /// Related SQLite: `sqlite3_result_error_toobig` 52 | @inlinable public func resultTooBigError() { 53 | sqlite3_result_error_toobig(rawValue) 54 | } 55 | 56 | /// Sets an error result indicating OOM. 57 | /// 58 | /// Related SQLite: `sqlite3_result_error_nomem` 59 | @inlinable public func resultNoMemoryError() { 60 | sqlite3_result_error_nomem(rawValue) 61 | } 62 | 63 | /// Sets an error result using the provided result code. 64 | /// 65 | /// Related SQLite: `sqlite3_result_error_code` 66 | @inlinable public func resultErrorCode(_ code: ResultCode) { 67 | sqlite3_result_error_code(rawValue, code.rawValue) 68 | } 69 | 70 | /// Sets the function result to a 32-bit integer. 71 | /// 72 | /// Related SQLite: `sqlite3_result_int` 73 | @inlinable public func resultInt(_ int: Int32) { 74 | sqlite3_result_int(rawValue, int) 75 | } 76 | 77 | /// Sets the function result to a 64-bit integer. 78 | /// 79 | /// Related SQLite: `sqlite3_result_int64` 80 | @inlinable public func resultInt64(_ int64: sqlite3_int64) { 81 | sqlite3_result_int64(rawValue, int64) 82 | } 83 | 84 | /// Sets the function result to NULL. 85 | /// 86 | /// Related SQLite: `sqlite3_result_null` 87 | @inlinable public func resultNull() { 88 | sqlite3_result_null(rawValue) 89 | } 90 | 91 | /// Sets the function result to UTF-8 text with optional destructor; length defaults to full string. 92 | /// 93 | /// Related SQLite: `sqlite3_result_text`, `SQLITE_STATIC`, `SQLITE_TRANSIENT` 94 | @inlinable public func resultText(_ text: UnsafePointer, length: Int32, destructor: ResultTextDestructor? = nil) { 95 | sqlite3_result_text(rawValue, text, length, destructor) 96 | } 97 | 98 | /// Sets the result text by copying it immediately (transient). 99 | /// 100 | /// Related SQLite: `sqlite3_result_text`, `SQLITE_TRANSIENT` 101 | @inlinable public func resultTransientText(_ text: UnsafePointer, length: Int32) { 102 | lsqlite3_result_transient_text(rawValue, text, length) 103 | } 104 | 105 | /// Sets the result text that remains valid for SQLite to use (static). 106 | /// 107 | /// Related SQLite: `sqlite3_result_text`, `SQLITE_STATIC` 108 | @inlinable public func resultStaticText(_ text: UnsafePointer, length: Int32) { 109 | lsqlite3_result_static_text(rawValue, text, length) 110 | } 111 | 112 | /// Copies an existing Value into the function result. 113 | /// 114 | /// Related SQLite: `sqlite3_result_value` 115 | @inlinable public func resultValue(_ value: Value) { 116 | sqlite3_result_value(rawValue, value.rawValue) 117 | } 118 | 119 | /// Sets the function result to a zero-filled blob of the given length. 120 | /// 121 | /// Related SQLite: `sqlite3_result_zeroblob` 122 | @inlinable public func resultZeroBlob(length: Int32) { 123 | sqlite3_result_zeroblob(rawValue, length) 124 | } 125 | 126 | @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) 127 | /// Assigns an application-defined subtype to the current result value. 128 | /// 129 | /// Related SQLite: `sqlite3_result_subtype` 130 | @inlinable public func resultSubtype(_ subtype: Subtype) { 131 | sqlite3_result_subtype(rawValue, subtype.rawValue) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Hooks.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Commit hook invoked before the transaction is finalized; return nonzero to roll back. 5 | /// 6 | /// Related SQLite: `sqlite3_commit_hook` 7 | public typealias CommitHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Int32 8 | 9 | /// Rollback hook invoked when a transaction is rolled back. 10 | /// 11 | /// Related SQLite: `sqlite3_rollback_hook` 12 | public typealias RollbackHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Void 13 | 14 | /// Update hook invoked for row changes on rowid tables. 15 | /// 16 | /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` 17 | public typealias UpdateHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ updateOperation: Int32, _ databaseName: UnsafePointer?, _ tableName: UnsafePointer?, _ rowID: sqlite3_int64) -> Void 18 | 19 | /// WAL hook invoked after a write transaction commits when using WAL mode. 20 | /// 21 | /// Related SQLite: `sqlite3_wal_hook` 22 | public typealias WALHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ databaseName: UnsafePointer?, _ pageInWALFileCount: Int32) -> Int32 23 | 24 | /// Return codes for commit hooks to continue or force a rollback. 25 | /// 26 | /// Related SQLite: `sqlite3_commit_hook` 27 | @frozen public struct CommitHookHandlerResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 28 | public let rawValue: Int32 29 | 30 | @inlinable public init(rawValue: Int32) { 31 | self.rawValue = rawValue 32 | } 33 | 34 | public static let `continue` = Self(rawValue: 0) 35 | public static let `break` = Self(rawValue: 1) 36 | 37 | /// Debug label for the commit hook decision. 38 | /// 39 | /// Related SQLite: `sqlite3_commit_hook` 40 | public var debugDescription: String { 41 | switch self { 42 | case .continue: return "LSQLITE_CONTINUE" 43 | case .break: return "LSQLITE_BREAK" 44 | default: return "CommitHookHandlerResult(rawValue: \(rawValue))" 45 | } 46 | } 47 | } 48 | 49 | /// Operation types reported to `updateHook`. 50 | /// 51 | /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` 52 | @frozen public struct UpdateOperation: Equatable, RawRepresentable, CustomDebugStringConvertible { 53 | public let rawValue: Int32 54 | 55 | @inlinable public init(rawValue: Int32) { 56 | self.rawValue = rawValue 57 | } 58 | 59 | public static let delete = Self(rawValue: SQLITE_DELETE) 60 | public static let insert = Self(rawValue: SQLITE_INSERT) 61 | public static let update = Self(rawValue: SQLITE_UPDATE) 62 | 63 | /// Debug label for the update operation. 64 | /// 65 | /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` 66 | public var debugDescription: String { 67 | switch self { 68 | case .delete: return "SQLITE_DELETE" 69 | case .insert: return "SQLITE_INSERT" 70 | case .update: return "SQLITE_UPDATE" 71 | default: return "UpdateOperation(rawValue: \(rawValue))" 72 | } 73 | } 74 | } 75 | 76 | /// Registers a commit hook; return `.break` to turn the commit into a rollback. 77 | /// - Parameters: 78 | /// - userData: Custom context passed to the handler. 79 | /// - commitHookHandler: Called before commit; `nil` clears the hook. 80 | /// - Returns: Previous user data pointer. 81 | /// 82 | /// Related SQLite: `sqlite3_commit_hook`, `sqlite3_step`, `COMMIT`, `ROLLBACK` 83 | @inlinable public func commitHook(_ userData: UnsafeMutableRawPointer? = nil, commitHookHandler: CommitHookHandler? = nil) -> UnsafeMutableRawPointer? { 84 | sqlite3_commit_hook(rawValue, commitHookHandler, userData) 85 | } 86 | 87 | /// Registers a rollback hook that fires whenever this connection rolls back. 88 | /// - Parameters: 89 | /// - userData: Custom context passed to the handler. 90 | /// - rollbackHookHandler: Handler invoked on rollback; `nil` clears the hook. 91 | /// - Returns: Previous user data pointer. 92 | /// 93 | /// Related SQLite: `sqlite3_rollback_hook`, `sqlite3_commit_hook`, `sqlite3_step` 94 | @inlinable public func rollbackHook(_ userData: UnsafeMutableRawPointer? = nil, rollbackHookHandler: RollbackHookHandler? = nil) -> UnsafeMutableRawPointer? { 95 | sqlite3_rollback_hook(rawValue, rollbackHookHandler, userData) 96 | } 97 | 98 | /// Registers a callback for row changes on rowid tables. 99 | /// - Parameters: 100 | /// - userData: Custom context passed to the handler. 101 | /// - updateHookHandler: Handler receiving operation, database/table names, and rowid; `nil` clears the hook. 102 | /// - Returns: Previous user data pointer. 103 | /// 104 | /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` 105 | @inlinable public func updateHook(_ userData: UnsafeMutableRawPointer? = nil, updateHookHandler: UpdateHookHandler? = nil) -> UnsafeMutableRawPointer? { 106 | sqlite3_update_hook(rawValue, updateHookHandler, userData) 107 | } 108 | 109 | /// Registers a callback fired after a WAL commit with the current WAL page count. 110 | /// - Parameters: 111 | /// - userData: Custom context passed to the handler. 112 | /// - walHookHandler: Handler invoked post-commit; `nil` clears the hook. 113 | /// - Returns: Previous user data pointer. 114 | /// 115 | /// Related SQLite: `sqlite3_wal_hook`, `sqlite3_wal_autocheckpoint`, `sqlite3_wal_checkpoint_v2` 116 | @inlinable public func walHook(_ userData: UnsafeMutableRawPointer? = nil, walHookHandler: WALHookHandler? = nil) -> UnsafeMutableRawPointer? { 117 | sqlite3_wal_hook(rawValue, walHookHandler, userData) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Function.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// C callback invoked to compute a scalar SQL function result. 5 | /// 6 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_user_data` 7 | public typealias FunctionFuncHandler = @convention(c) (_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) -> Void 8 | 9 | /// C callback invoked for each row of an aggregate or window function. 10 | /// 11 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function` 12 | public typealias FunctionStepHandler = @convention(c) (_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) -> Void 13 | 14 | /// Finalizer for aggregate or window function state. 15 | /// 16 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function` 17 | public typealias FunctionFinalHandler = @convention(c) (_ context: OpaquePointer?) -> Void 18 | 19 | /// Window function callback that produces a value for the current frame. 20 | /// 21 | /// Related SQLite: `sqlite3_create_window_function` 22 | public typealias FunctionValueHandler = @convention(c) (_ context: OpaquePointer?) -> Void 23 | 24 | /// Window function callback that reverses a prior step invocation. 25 | /// 26 | /// Related SQLite: `sqlite3_create_window_function` 27 | public typealias FunctionInverseHandler = @convention(c) (_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) -> Void 28 | 29 | /// Cleanup callback executed when a user-defined function is destroyed. 30 | /// 31 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function` 32 | public typealias FunctionDestroyHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Void 33 | 34 | /// Options describing text encoding and determinism for user-defined SQL functions. 35 | /// 36 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` 37 | @frozen public struct FunctionFlag: Equatable, OptionSet, CustomDebugStringConvertible { 38 | public let rawValue: Int32 39 | 40 | @inlinable public init(rawValue: Int32) { 41 | self.rawValue = rawValue 42 | } 43 | 44 | public static let utf8 = Self(rawValue: SQLITE_UTF8) 45 | public static let utf16le = Self(rawValue: SQLITE_UTF16LE) 46 | public static let utf16be = Self(rawValue: SQLITE_UTF16BE) 47 | public static let utf16 = Self(rawValue: SQLITE_UTF16) 48 | public static let any = Self(rawValue: SQLITE_ANY) 49 | public static let utf16Aligned = Self(rawValue: SQLITE_UTF16_ALIGNED) 50 | 51 | public static let deterministic = Self(rawValue: SQLITE_DETERMINISTIC) 52 | 53 | /// Debug label for the function flag value. 54 | /// 55 | /// Related SQLite: `sqlite3_create_function_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` 56 | public var debugDescription: String { 57 | switch self { 58 | case .utf8: return "SQLITE_UTF8" 59 | case .utf16le: return "SQLITE_UTF16LE" 60 | case .utf16be: return "SQLITE_UTF16BE" 61 | case .utf16: return "SQLITE_UTF16" 62 | case .any: return "SQLITE_ANY" 63 | case .utf16Aligned: return "SQLITE_UTF16_ALIGNED" 64 | default: return "FunctionFlag(rawValue: \(rawValue))" 65 | } 66 | } 67 | } 68 | 69 | /// Registers or redefines a scalar or aggregate SQL function on this connection. 70 | /// - Parameters: 71 | /// - name: Function name (UTF-8). 72 | /// - argumentCount: Allowed argument count; `-1` permits variable arguments. 73 | /// - flag: Encoding and determinism options. 74 | /// - userData: Context pointer available via `Context.userData`. 75 | /// - funcHandler: Scalar implementation; set when defining a scalar function. 76 | /// - stepHandler: Aggregate step implementation; set when defining an aggregate or window function. 77 | /// - finalHandler: Aggregate finalizer; set when defining an aggregate or window function. 78 | /// - destroyHandler: Optional cleanup for `userData` when the function is replaced or the connection closes. 79 | /// - Returns: Result of `sqlite3_create_function_v2`. 80 | /// 81 | /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_user_data`, `SQLITE_DETERMINISTIC`, `SQLITE_LIMIT_FUNCTION_ARG` 82 | @inlinable public func createFunction(name: UnsafePointer, argumentCount: Int32, flags flag: FunctionFlag, userData: UnsafeMutableRawPointer? = nil, funcHandler: FunctionFuncHandler? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { 83 | sqlite3_create_function_v2(rawValue, name, argumentCount, flag.rawValue, userData, funcHandler, stepHandler, finalHandler, destroyHandler).resultCode 84 | } 85 | 86 | /// Registers or redefines an aggregate or window function on this connection. 87 | /// - Parameters: 88 | /// - name: Function name (UTF-8). 89 | /// - argumentCount: Allowed argument count; `-1` permits variable arguments. 90 | /// - flag: Encoding and determinism options. 91 | /// - userData: Context pointer available via `Context.userData`. 92 | /// - stepHandler: Aggregate step implementation (required). 93 | /// - finalHandler: Aggregate finalizer (required). 94 | /// - valueHandler: Window value callback; set with `inverseHandler` for window behavior. 95 | /// - inverseHandler: Window inverse step; set with `valueHandler` for window behavior. 96 | /// - destroyHandler: Optional cleanup for `userData` when the function is dropped. 97 | /// - Returns: Result of `sqlite3_create_window_function`. 98 | /// 99 | /// Related SQLite: `sqlite3_create_window_function`, `sqlite3_create_function_v2`, `sqlite3_user_data` 100 | @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) 101 | @inlinable public func createWindowFunction(name: UnsafePointer, argumentCount: Int32, flags flag: FunctionFlag, userData: UnsafeMutableRawPointer? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, valueHandler: FunctionValueHandler? = nil, inverseHandler: FunctionInverseHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { 102 | sqlite3_create_window_function(rawValue, name, argumentCount, flag.rawValue, userData, stepHandler, finalHandler, valueHandler, inverseHandler, destroyHandler).resultCode 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSQLite 2 | 3 | A zero-overhead, typed Swift wrapper around the SQLite C API — same functions, no `OpaquePointer`, no magic constants. 4 | 5 | ## Motivation 6 | 7 | The SQLite C API is small and powerful, but in Swift it comes with a few pain points: 8 | - You work with `OpaquePointer` handles that are easy to misuse. 9 | - You constantly look up which `Int32` constant to pass or compare against. 10 | - In Swift, many `#define` constants become computed variables, hiding the fact that they are real constants. 11 | - The default Swift `SQLite3` module does not expose any inline documentation, because SQLite’s comments are not in the format Swift recognizes for doc comments. 12 | 13 | LSQLite keeps the *exact* SQLite API surface and behavior, but: 14 | - Wraps raw handles like `sqlite3 *` and `sqlite3_stmt *` into small Swift structs (`Database`, `Statement`, …). 15 | - Wraps result codes and flags into typed Swift values (`ResultCode`, `OpenFlag`, …). 16 | - Leaves control flow and error handling exactly as in the C API: you still check result codes instead of catching errors. 17 | 18 | If you already know the SQLite C API, nothing new to learn — the names and concepts are the same, just expressed as Swift types. 19 | 20 | ## Design Goals 21 | 22 | - 1:1 mapping of the SQLite C API (names and behavior stay the same). 23 | - No new abstractions: only wrappers around existing C functions, handles, flags, and result codes. 24 | - Non-throwing API: you continue to work with SQLite-style result codes (`SQLITE_OK`, `SQLITE_DONE`, …) via `ResultCode`. 25 | - Strong typing: Swift wrappers around `OpaquePointer`, integers, and bitmask flags. 26 | - Zero overhead: wrapper functions are `@inlinable` one-liners that forward to `sqlite3_*`; the generated machine code is the same as calling the C API directly. 27 | - Potentially faster than using the imported C module directly: 28 | - SQLite `#define` constants that Swift normally imports as computed variables are exposed here as true Swift constants (`static let` and enums). 29 | - The compiler knows this at compile time and can better optimize branches and flag checks. 30 | - Easy interop: you can always go back to the raw C API using `rawValue`, or come from the C API by constructing LSQLite types from existing handles and codes. 31 | - Fully documented API: every public symbol in LSQLite has carefully written Swift documentation comments, each with a list of related SQLite functions and constants, visible right in Xcode, Cursor, or any IDE that supports Swift doc popups. 32 | 33 | ## Documentation 34 | 35 | All LSQLite symbols correspond directly to the SQLite C API and come with new inline Swift documentation: 36 | - Each wrapper type and method has a concise description written for Swift developers, not a verbatim copy of SQLite’s comments. 37 | - Every doc comment includes a “Related SQLite:” section listing the original functions and constants it wraps, so you can easily find the upstream documentation for deeper details. 38 | - Your IDE (Xcode, Cursor, etc.) shows this documentation in-place for code completion and quick help. 39 | - You get SQLite context and cross-references without leaving the editor or switching to external references. 40 | 41 | ## Installation 42 | 43 | ### Swift Package Manager 44 | 45 | Add LSQLite to your `Package.swift`: 46 | 47 | ```swift 48 | // swift-tools-version:5.2 49 | import PackageDescription 50 | 51 | let package = Package( 52 | // ... 53 | dependencies: [ 54 | .package(url: "https://github.com/antonsergeev88/LSQLite.git", from: "3.51.0") 55 | ], 56 | targets: [ 57 | .target( 58 | name: "YourApp", 59 | dependencies: [ 60 | .product(name: "LSQLite", package: "LSQLite") 61 | ] 62 | ), 63 | ] 64 | ) 65 | ``` 66 | 67 | In Xcode, you can also add it via: 68 | - `File` → `Add Packages…` 69 | - Enter the repository URL (https://github.com/antonsergeev88/LSQLite.git) for LSQLite and add the `LSQLite` product to your target. 70 | 71 | ## Quick Start 72 | 73 | ```swift 74 | import LSQLite 75 | 76 | // 1. Open database 77 | var db: Database? 78 | let openResult = Database.open(&db, at: .init(rawValue: databasePath), withOpenFlags: [.readwrite, .create]) 79 | guard openResult == .ok, let db else { 80 | fatalError("Failed to open database: \(openResult)") 81 | } 82 | 83 | // 2. Create a table 84 | let createResult = db.exec("CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT);") 85 | guard createResult == .ok else { 86 | fatalError("Failed to create table: \(createResult)") 87 | } 88 | 89 | // 3. Insert a row with a prepared statement 90 | var insert: Statement? 91 | guard Statement.prepare(&insert, sql: "INSERT INTO users(id, name) VALUES(?, ?);", for: db) == .ok, let insert else { 92 | fatalError("Failed to prepare insert statement") 93 | } 94 | insert.bindInt(1, at: 1) 95 | insert.bindTransientText("Natalie", at: 2) 96 | guard insert.step() == .done else { 97 | fatalError("Insert failed") 98 | } 99 | insert.finalize() 100 | 101 | // 4. Close database 102 | db.close() 103 | ``` 104 | 105 | Every function you see here has a direct C counterpart (`sqlite3_open_v2`, `sqlite3_exec`, `sqlite3_prepare_v2`, `sqlite3_bind_int`, `sqlite3_bind_text`, `sqlite3_step`, `sqlite3_finalize`, `sqlite3_close`). LSQLite just wraps them in typed, object-style Swift. 106 | 107 | ## Migrating from the SQLite C API 108 | 109 | Each SQLite C function has an LSQLite equivalent that keeps the same semantics but replaces raw pointers and integers with Swift types. 110 | 111 | ### Opening a database 112 | 113 | **Swift with SQLite C API** 114 | 115 | ```swift 116 | import SQLite3 117 | var db: OpaquePointer? 118 | let rc = sqlite3_open_v2("test.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nil) 119 | if rc != SQLITE_OK { /* handle error */ } 120 | ``` 121 | 122 | **Swift with LSQLite** 123 | 124 | ```swift 125 | import LSQLite 126 | var db: Database? 127 | let rc = Database.open(&db, at: .init(rawValue: "test.db"), withOpenFlags: [.readwrite, .create]) 128 | if rc != .ok { /* handle error */ } 129 | ``` 130 | 131 | ### Binding integers and text 132 | 133 | **Swift with SQLite C API** 134 | 135 | ```swift 136 | var stmt: OpaquePointer? 137 | sqlite3_prepare_v2(db, "INSERT INTO t(id, name) VALUES(?, ?);", -1, &stmt, nil) 138 | sqlite3_bind_int(stmt, 1, 42) 139 | sqlite3_bind_text(stmt, 2, "Alice", -1, SQLITE_TRANSIENT) 140 | sqlite3_step(stmt) 141 | ``` 142 | 143 | **Swift with LSQLite** 144 | 145 | ```swift 146 | var stmt: Statement? 147 | Statement.prepare(&stmt, sql: "INSERT INTO t(id, name) VALUES(?, ?);", for: db) 148 | stmt!.bindInt(42, at: 1) 149 | stmt!.bindTransientText("Alice", at: 2) 150 | stmt!.step() 151 | ``` 152 | 153 | The control flow is identical: prepare → bind → step. You just use typed `Statement` and `ResultCode` instead of raw `sqlite3_stmt *` and `Int32`. 154 | 155 | ## Interoperability with the SQLite C API 156 | 157 | You can drop down to the SQLite C API or back to LSQLite at any time. 158 | 159 | From LSQLite to C: 160 | 161 | ```swift 162 | let rawDb: OpaquePointer = db.rawValue 163 | let rawCode: Int32 = db.close().rawValue 164 | ``` 165 | 166 | From C to LSQLite: 167 | 168 | ```swift 169 | let db = Database(rawValue: someSQLitePointer) 170 | let code = ResultCode(rawValue: SQLITE_BUSY) 171 | ``` 172 | 173 | This means you can: 174 | - Adopt LSQLite gradually in an existing codebase. 175 | - Use LSQLite for most code, while still calling any specialized `sqlite3_*` function that is not wrapped yet. 176 | - Move back to pure C-style code whenever you want, without being locked in. 177 | 178 | ## Non-Goals 179 | 180 | LSQLite intentionally does *not*: 181 | - Provide an ORM, query builder, or higher-level database abstraction. 182 | - Hide SQLite’s result codes behind `throw`/`try` or a custom error model. 183 | - Change SQLite’s behavior or add automatic migrations. 184 | 185 | It is a low-level, but safer and more readable, Swift presentation of the original C API. 186 | 187 | ## Performance and Safety 188 | 189 | - **Zero overhead:** Public APIs are `@inlinable` and forward directly to `sqlite3_*` calls. After inlining, your binary contains the same code as if you had called the C API yourself. 190 | - **Better constants:** SQLite `#define` values are exposed as real Swift constants, not computed variables. The compiler can see this at compile time, fold expressions, and better optimize flag checks. 191 | - **Type safety:** Typed wrappers (`Database`, `Statement`, `ResultCode`, `OpenFlag`, etc.) make it harder to pass the wrong pointer or constant. 192 | 193 | ## Contributing 194 | 195 | Issues and pull requests are welcome. 196 | - Keep wrappers thin: new APIs should stay close to the C API in naming and behavior. 197 | - SQLite is large; inline documentation can have gaps or mistakes. Issues and PRs that improve or fix doc comments are among the most valuable contributions. 198 | 199 | ## License 200 | 201 | LSQLite is available under the MIT License. See `LICENSE` for details. 202 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+Authorizer.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | extension Database { 4 | /// Authorization callback invoked during statement compilation. 5 | /// 6 | /// Related SQLite: `sqlite3_set_authorizer` 7 | public typealias AuthorizerHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ actionCode: Int32, UnsafePointer?, UnsafePointer?, _ databaseName: UnsafePointer?, _ triggerOrViewName: UnsafePointer?) -> Int32 8 | 9 | /// Return codes for an authorizer callback to allow, deny, or ignore an action. 10 | /// 11 | /// Related SQLite: `sqlite3_set_authorizer`, `SQLITE_OK`, `SQLITE_DENY`, `SQLITE_IGNORE`, `sqlite3_vtab_on_conflict` 12 | @frozen public struct AuthorizerHandlerResult: Equatable, RawRepresentable, CustomDebugStringConvertible { 13 | public let rawValue: Int32 14 | 15 | @inlinable public init(rawValue: Int32) { 16 | self.rawValue = rawValue 17 | } 18 | 19 | public static let ok = Self(rawValue: SQLITE_OK) 20 | /// Abort the SQL statement with an error 21 | public static let deny = Self(rawValue: SQLITE_DENY) 22 | /// Don't allow access, but don't generate an error 23 | public static let ignore = Self(rawValue: SQLITE_IGNORE) 24 | 25 | /// Debug label for an authorizer decision. 26 | /// 27 | /// Related SQLite: `sqlite3_set_authorizer`, `SQLITE_OK`, `SQLITE_DENY`, `SQLITE_IGNORE` 28 | public var debugDescription: String { 29 | switch self { 30 | case .ok: return "SQLITE_OK" 31 | case .deny: return "SQLITE_DENY" 32 | case .ignore: return "SQLITE_IGNORE" 33 | default: return "AuthorizerHandlerResult(rawValue: \(rawValue))" 34 | } 35 | } 36 | } 37 | 38 | /// Action codes describing what operation is being authorized in `setAuthorizerHandler`. 39 | /// 40 | /// Related SQLite: `sqlite3_set_authorizer`, `SQLITE_CREATE_INDEX`, `SQLITE_DROP_TABLE`, `SQLITE_SELECT` 41 | @frozen public struct AuthorizerHandlerActionCode: Equatable, RawRepresentable, CustomDebugStringConvertible { 42 | public let rawValue: Int32 43 | 44 | @inlinable public init(rawValue: Int32) { 45 | self.rawValue = rawValue 46 | } 47 | 48 | /// Index Name Table Name 49 | public static let createIndex = Self(rawValue: SQLITE_CREATE_INDEX) 50 | /// Table Name NULL 51 | public static let createTable = Self(rawValue: SQLITE_CREATE_TABLE) 52 | /// Index Name Table Name 53 | public static let createTempIndex = Self(rawValue: SQLITE_CREATE_TEMP_INDEX) 54 | /// Table Name NULL 55 | public static let createTempTable = Self(rawValue: SQLITE_CREATE_TEMP_TABLE) 56 | /// Trigger Name Table Name 57 | public static let createTempTrigger = Self(rawValue: SQLITE_CREATE_TEMP_TRIGGER) 58 | /// View Name NULL 59 | public static let createTempView = Self(rawValue: SQLITE_CREATE_TEMP_VIEW) 60 | /// Trigger Name Table Name 61 | public static let createTrigger = Self(rawValue: SQLITE_CREATE_TRIGGER) 62 | /// View Name NULL 63 | public static let createView = Self(rawValue: SQLITE_CREATE_VIEW) 64 | /// Table Name NULL 65 | public static let delete = Self(rawValue: SQLITE_DELETE) 66 | /// Index Name Table Name 67 | public static let dropIndex = Self(rawValue: SQLITE_DROP_INDEX) 68 | /// Table Name NULL 69 | public static let dropTable = Self(rawValue: SQLITE_DROP_TABLE) 70 | /// Index Name Table Name 71 | public static let dropTempIndex = Self(rawValue: SQLITE_DROP_TEMP_INDEX) 72 | /// Table Name NULL 73 | public static let dropTempTable = Self(rawValue: SQLITE_DROP_TEMP_TABLE) 74 | /// Trigger Name Table Name 75 | public static let dropTempTrigger = Self(rawValue: SQLITE_DROP_TEMP_TRIGGER) 76 | /// View Name NULL 77 | public static let dropTempView = Self(rawValue: SQLITE_DROP_TEMP_VIEW) 78 | /// Trigger Name Table Name 79 | public static let dropTrigger = Self(rawValue: SQLITE_DROP_TRIGGER) 80 | /// View Name NULL 81 | public static let dropView = Self(rawValue: SQLITE_DROP_VIEW) 82 | /// Table Name NULL 83 | public static let insert = Self(rawValue: SQLITE_INSERT) 84 | /// Pragma Name 1st arg or NULL 85 | public static let pragma = Self(rawValue: SQLITE_PRAGMA) 86 | /// Table Name Column Name 87 | public static let read = Self(rawValue: SQLITE_READ) 88 | /// NULL NULL 89 | public static let select = Self(rawValue: SQLITE_SELECT) 90 | /// Operation NULL 91 | public static let transaction = Self(rawValue: SQLITE_TRANSACTION) 92 | /// Table Name Column Name 93 | public static let update = Self(rawValue: SQLITE_UPDATE) 94 | /// Filename NULL 95 | public static let attach = Self(rawValue: SQLITE_ATTACH) 96 | /// Database Name NULL 97 | public static let detach = Self(rawValue: SQLITE_DETACH) 98 | /// Database Name Table Name 99 | public static let alterTable = Self(rawValue: SQLITE_ALTER_TABLE) 100 | /// Index Name NULL 101 | public static let reindex = Self(rawValue: SQLITE_REINDEX) 102 | /// Table Name NULL 103 | public static let analyze = Self(rawValue: SQLITE_ANALYZE) 104 | /// Table Name Module Name 105 | public static let createVTable = Self(rawValue: SQLITE_CREATE_VTABLE) 106 | /// Table Name Module Name 107 | public static let dropVTable = Self(rawValue: SQLITE_DROP_VTABLE) 108 | /// NULL Function Name 109 | public static let function = Self(rawValue: SQLITE_FUNCTION) 110 | /// Operation Savepoint Name 111 | public static let savepoint = Self(rawValue: SQLITE_SAVEPOINT) 112 | /// No longer used 113 | public static let copy = Self(rawValue: SQLITE_COPY) 114 | /// NULL NULL 115 | public static let recursive = Self(rawValue: SQLITE_RECURSIVE) 116 | 117 | /// Debug label for the authorizer action code. 118 | /// 119 | /// Related SQLite: `sqlite3_set_authorizer`, `SQLITE_*` 120 | public var debugDescription: String { 121 | switch self { 122 | case .createIndex: return "SQLITE_CREATE_INDEX" 123 | case .createTable: return "SQLITE_CREATE_TABLE" 124 | case .createTempIndex: return "SQLITE_CREATE_TEMP_INDEX" 125 | case .createTempTable: return "SQLITE_CREATE_TEMP_TABLE" 126 | case .createTempTrigger: return "SQLITE_CREATE_TEMP_TRIGGER" 127 | case .createTempView: return "SQLITE_CREATE_TEMP_VIEW" 128 | case .createTrigger: return "SQLITE_CREATE_TRIGGER" 129 | case .createView: return "SQLITE_CREATE_VIEW" 130 | case .delete: return "SQLITE_DELETE" 131 | case .dropIndex: return "SQLITE_DROP_INDEX" 132 | case .dropTable: return "SQLITE_DROP_TABLE" 133 | case .dropTempIndex: return "SQLITE_DROP_TEMP_INDEX" 134 | case .dropTempTable: return "SQLITE_DROP_TEMP_TABLE" 135 | case .dropTempTrigger: return "SQLITE_DROP_TEMP_TRIGGER" 136 | case .dropTempView: return "SQLITE_DROP_TEMP_VIEW" 137 | case .dropTrigger: return "SQLITE_DROP_TRIGGER" 138 | case .dropView: return "SQLITE_DROP_VIEW" 139 | case .insert: return "SQLITE_INSERT" 140 | case .pragma: return "SQLITE_PRAGMA" 141 | case .read: return "SQLITE_READ" 142 | case .select: return "SQLITE_SELECT" 143 | case .transaction: return "SQLITE_TRANSACTION" 144 | case .update: return "SQLITE_UPDATE" 145 | case .attach: return "SQLITE_ATTACH" 146 | case .detach: return "SQLITE_DETACH" 147 | case .alterTable: return "SQLITE_ALTER_TABLE" 148 | case .reindex: return "SQLITE_REINDEX" 149 | case .analyze: return "SQLITE_ANALYZE" 150 | case .createVTable: return "SQLITE_CREATE_VTABLE" 151 | case .dropVTable: return "SQLITE_DROP_VTABLE" 152 | case .function: return "SQLITE_FUNCTION" 153 | case .savepoint: return "SQLITE_SAVEPOINT" 154 | case .copy: return "SQLITE_COPY" 155 | case .recursive: return "SQLITE_RECURSIVE" 156 | default: return "AuthorizerHandlerActionCode(rawValue: \(rawValue))" 157 | } 158 | } 159 | } 160 | 161 | /// Registers or clears a compile-time authorizer invoked while statements are prepared. 162 | /// - Parameters: 163 | /// - userData: Custom context passed to the authorizer. 164 | /// - handler: Callback returning `.ok`, `.deny`, or `.ignore`; `nil` disables authorization. 165 | /// - Returns: Result of `sqlite3_set_authorizer`. 166 | /// 167 | /// Related SQLite: `sqlite3_set_authorizer`, `sqlite3_prepare_v2`, `SQLITE_DENY`, `SQLITE_IGNORE` 168 | @inlinable public func setAuthorizerHandler(userData: UnsafeMutableRawPointer? = nil, _ handler: AuthorizerHandler? = nil) -> ResultCode { 169 | sqlite3_set_authorizer(rawValue, handler, userData).resultCode 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Sources/LSQLite/Database/Database+FileNameURI.swift: -------------------------------------------------------------------------------- 1 | extension Database.FileName { 2 | /// Components used to build SQLite URI filenames. 3 | /// 4 | /// Related SQLite: "URI filenames", `sqlite3_open_v2`, `SQLITE_OPEN_URI` 5 | public enum URI { 6 | /// URI scheme component. 7 | /// 8 | /// Note: SQLite only interprets URIs that use the `"file"` scheme. 9 | /// 10 | /// Related SQLite: "URI filenames", `"file:"` 11 | @frozen public struct Scheme: RawRepresentable, Sendable { 12 | public let rawValue: String 13 | 14 | @inlinable public init(rawValue: String) { 15 | self.rawValue = rawValue 16 | } 17 | 18 | /// SQLite file URI scheme. 19 | /// 20 | /// Related SQLite: `"file:"` 21 | public static let file = Self(rawValue: "file") 22 | } 23 | 24 | /// URI authority component. 25 | /// 26 | /// Related SQLite: "URI filenames (authority)" 27 | @frozen public struct Authority: RawRepresentable, Sendable { 28 | public let rawValue: String 29 | 30 | @inlinable public init(rawValue: String) { 31 | self.rawValue = rawValue 32 | } 33 | 34 | /// Empty authority (`file:///path`). 35 | /// 36 | /// Related SQLite: "URI filenames (authority)" 37 | public static let empty = Self(rawValue: "") 38 | 39 | /// `localhost` authority (`file://localhost/path`). 40 | /// 41 | /// Related SQLite: "URI filenames (authority)" 42 | public static let localhost = Self(rawValue: "localhost") 43 | } 44 | 45 | /// URI path component. 46 | /// 47 | /// Related SQLite: "URI filenames (path)", `sqlite3_open_v2` 48 | @frozen public struct Path: RawRepresentable, Sendable { 49 | public let rawValue: String 50 | 51 | @inlinable public init(rawValue: String) { 52 | self.rawValue = rawValue 53 | } 54 | 55 | /// Creates a path component by escaping delimiters reserved by SQLite URI parsing. 56 | /// - Parameter unescaped: Path text that will be percent-escaped for `?`, `#`, and `%`. 57 | /// 58 | /// Related SQLite: "URI filenames (path)", escape sequences (`%HH`) 59 | @inlinable public init(_ unescaped: String) { 60 | self.rawValue = URI.percentEncode(unescaped, escaping: { byte in 61 | byte == UInt8(ascii: "?") || byte == UInt8(ascii: "#") || byte == UInt8(ascii: "%") 62 | }) 63 | } 64 | 65 | @inlinable func requiringLeadingSlash() -> Self { 66 | guard !rawValue.isEmpty, !rawValue.hasPrefix("/") else { return self } 67 | return Self(rawValue: "/" + rawValue) 68 | } 69 | } 70 | 71 | /// URI query component (without the leading `?`). 72 | /// 73 | /// Related SQLite: "URI filenames (query string)" 74 | @frozen public struct Query: RawRepresentable, Sendable { 75 | public let rawValue: String 76 | 77 | @inlinable public init(rawValue: String) { 78 | self.rawValue = rawValue 79 | } 80 | 81 | /// Creates a query component from query parameters. 82 | /// 83 | /// Related SQLite: "URI filenames (query parameters)" 84 | @inlinable public init(_ parameters: [QueryParameter]) { 85 | var result = "" 86 | result.reserveCapacity(parameters.count * 8) 87 | for (index, parameter) in parameters.enumerated() { 88 | if index != 0 { 89 | result.append("&") 90 | } 91 | result.append(parameter.key.rawValue) 92 | result.append("=") 93 | result.append(parameter.value.rawValue) 94 | } 95 | self.rawValue = result 96 | } 97 | } 98 | 99 | /// URI fragment component (without the leading `#`). 100 | /// 101 | /// Note: SQLite ignores fragments in URI filenames. 102 | /// 103 | /// Related SQLite: "URI filenames (fragment)" 104 | @frozen public struct Fragment: RawRepresentable, Sendable { 105 | public let rawValue: String 106 | 107 | @inlinable public init(rawValue: String) { 108 | self.rawValue = rawValue 109 | } 110 | 111 | /// Creates a fragment component by escaping `%`. 112 | /// 113 | /// Related SQLite: "URI filenames (fragment)", escape sequences (`%HH`) 114 | @inlinable public init(_ unescaped: String) { 115 | self.rawValue = URI.percentEncode(unescaped, escaping: { $0 == UInt8(ascii: "%") }) 116 | } 117 | } 118 | 119 | /// URI query parameter key. 120 | /// 121 | /// Related SQLite: "URI filenames (query parameters)" 122 | @frozen public struct QueryKey: RawRepresentable, Sendable { 123 | public let rawValue: String 124 | 125 | @inlinable public init(rawValue: String) { 126 | self.rawValue = rawValue 127 | } 128 | 129 | /// Creates a key by escaping `%`, `&`, `=`, and `#`. 130 | /// 131 | /// Related SQLite: "URI filenames (query parameters)", escape sequences (`%HH`) 132 | @inlinable public init(_ unescaped: String) { 133 | self.rawValue = URI.percentEncode(unescaped, escaping: { byte in 134 | byte == UInt8(ascii: "%") 135 | || byte == UInt8(ascii: "&") 136 | || byte == UInt8(ascii: "=") 137 | || byte == UInt8(ascii: "#") 138 | }) 139 | } 140 | } 141 | 142 | /// URI query parameter value. 143 | /// 144 | /// Related SQLite: "URI filenames (query parameters)" 145 | @frozen public struct QueryValue: RawRepresentable, Sendable { 146 | public let rawValue: String 147 | 148 | @inlinable public init(rawValue: String) { 149 | self.rawValue = rawValue 150 | } 151 | 152 | /// Creates a value by escaping `%`, `&`, `=`, and `#`. 153 | /// 154 | /// Related SQLite: "URI filenames (query parameters)", escape sequences (`%HH`) 155 | @inlinable public init(_ unescaped: String) { 156 | self.rawValue = URI.percentEncode(unescaped, escaping: { byte in 157 | byte == UInt8(ascii: "%") 158 | || byte == UInt8(ascii: "&") 159 | || byte == UInt8(ascii: "=") 160 | || byte == UInt8(ascii: "#") 161 | }) 162 | } 163 | } 164 | 165 | /// URI query parameter key/value pair. 166 | /// 167 | /// Related SQLite: "URI filenames (query parameters)" 168 | @frozen public struct QueryParameter: Sendable { 169 | public let key: QueryKey 170 | public let value: QueryValue 171 | 172 | @inlinable public init(key: QueryKey, value: QueryValue) { 173 | self.key = key 174 | self.value = value 175 | } 176 | 177 | /// Creates a `cache=` query parameter. 178 | /// 179 | /// Related SQLite: `cache=shared`, `cache=private` 180 | @inlinable public static func cache(_ cache: Cache) -> Self { 181 | Self(key: QueryKey(rawValue: "cache"), value: QueryValue(rawValue: cache.rawValue)) 182 | } 183 | 184 | /// Creates an `immutable=` query parameter. 185 | /// 186 | /// Related SQLite: `immutable=1`, `SQLITE_IOCAP_IMMUTABLE` 187 | @inlinable public static func immutable(_ value: Boolean) -> Self { 188 | Self(key: QueryKey(rawValue: "immutable"), value: QueryValue(rawValue: value.rawValue)) 189 | } 190 | 191 | /// Creates a `mode=` query parameter. 192 | /// 193 | /// Related SQLite: `mode=ro`, `mode=rw`, `mode=rwc`, `mode=memory` 194 | @inlinable public static func mode(_ mode: Mode) -> Self { 195 | Self(key: QueryKey(rawValue: "mode"), value: QueryValue(rawValue: mode.rawValue)) 196 | } 197 | 198 | /// Creates a `modeof=` query parameter. 199 | /// 200 | /// Related SQLite: `modeof=filename` 201 | @inlinable public static func modeof(_ filename: QueryValue) -> Self { 202 | Self(key: QueryKey(rawValue: "modeof"), value: filename) 203 | } 204 | 205 | /// Creates a `nolock=` query parameter. 206 | /// 207 | /// Related SQLite: `nolock=1` 208 | @inlinable public static func nolock(_ value: Boolean) -> Self { 209 | Self(key: QueryKey(rawValue: "nolock"), value: QueryValue(rawValue: value.rawValue)) 210 | } 211 | 212 | /// Creates a `psow=` query parameter. 213 | /// 214 | /// Related SQLite: `psow=0`, `psow=1` 215 | @inlinable public static func psow(_ value: Boolean) -> Self { 216 | Self(key: QueryKey(rawValue: "psow"), value: QueryValue(rawValue: value.rawValue)) 217 | } 218 | 219 | /// Creates a `vfs=` query parameter. 220 | /// 221 | /// Related SQLite: `vfs=NAME`, `sqlite3_vfs_register` 222 | @inlinable public static func vfs(_ vfs: VFS) -> Self { 223 | Self(key: QueryKey(rawValue: "vfs"), value: QueryValue(rawValue: vfs.rawValue)) 224 | } 225 | } 226 | 227 | /// Value for the `mode=` query parameter. 228 | /// 229 | /// Related SQLite: `mode=ro`, `mode=rw`, `mode=rwc`, `mode=memory` 230 | @frozen public struct Mode: RawRepresentable, Sendable { 231 | public let rawValue: String 232 | 233 | @inlinable public init(rawValue: String) { 234 | self.rawValue = rawValue 235 | } 236 | 237 | public static let ro = Self(rawValue: "ro") 238 | public static let rw = Self(rawValue: "rw") 239 | public static let rwc = Self(rawValue: "rwc") 240 | public static let memory = Self(rawValue: "memory") 241 | } 242 | 243 | /// Value for the `cache=` query parameter. 244 | /// 245 | /// Related SQLite: `cache=shared`, `cache=private` 246 | @frozen public struct Cache: RawRepresentable, Sendable { 247 | public let rawValue: String 248 | 249 | @inlinable public init(rawValue: String) { 250 | self.rawValue = rawValue 251 | } 252 | 253 | public static let shared = Self(rawValue: "shared") 254 | public static let `private` = Self(rawValue: "private") 255 | } 256 | 257 | /// Boolean query parameter value wrapper. 258 | /// 259 | /// Related SQLite: boolean query parameters such as `immutable=1`, `nolock=1`, `psow=1` 260 | @frozen public struct Boolean: RawRepresentable, Sendable { 261 | public let rawValue: String 262 | 263 | @inlinable public init(rawValue: String) { 264 | self.rawValue = rawValue 265 | } 266 | 267 | public static let enabled = Self(rawValue: "1") 268 | public static let disabled = Self(rawValue: "0") 269 | } 270 | 271 | /// Value for the `vfs=` query parameter. 272 | /// 273 | /// Related SQLite: `vfs=NAME`, `sqlite3_vfs_register` 274 | @frozen public struct VFS: RawRepresentable, Sendable { 275 | public let rawValue: String 276 | 277 | @inlinable public init(rawValue: String) { 278 | self.rawValue = rawValue 279 | } 280 | } 281 | 282 | @usableFromInline 283 | static func percentEncode(_ string: String, escaping shouldEscape: (UInt8) -> Bool) -> String { 284 | var needsEscaping = false 285 | for byte in string.utf8 { 286 | if shouldEscape(byte) { 287 | needsEscaping = true 288 | break 289 | } 290 | } 291 | guard needsEscaping else { return string } 292 | 293 | var result = "" 294 | result.reserveCapacity(string.utf8.count) 295 | for scalar in string.unicodeScalars { 296 | guard scalar.isASCII else { 297 | result.unicodeScalars.append(scalar) 298 | continue 299 | } 300 | 301 | let byte = UInt8(scalar.value) 302 | if shouldEscape(byte) { 303 | result.append("%") 304 | result.append(Self.hexDigitUppercase(byte >> 4)) 305 | result.append(Self.hexDigitUppercase(byte & 0x0F)) 306 | } else { 307 | result.unicodeScalars.append(scalar) 308 | } 309 | } 310 | return result 311 | } 312 | 313 | @usableFromInline 314 | static func hexDigitUppercase(_ nibble: UInt8) -> Character { 315 | let nibble = nibble & 0x0F 316 | let base: UInt32 = nibble < 10 ? 48 : 55 317 | let value = base + UInt32(nibble) 318 | return Character(Unicode.Scalar(value)!) 319 | } 320 | } 321 | 322 | /// Creates a filename wrapper from URI components. 323 | /// 324 | /// The resulting string is suitable for passing into `sqlite3_open_v2` when URI processing is enabled. 325 | /// 326 | /// Related SQLite: "URI filenames", `sqlite3_open_v2`, `SQLITE_OPEN_URI` 327 | @inlinable public static func uri( 328 | scheme: URI.Scheme = .file, 329 | path: URI.Path, 330 | query: URI.Query? = nil, 331 | fragment: URI.Fragment? = nil 332 | ) -> Self { 333 | Self._uri(scheme: scheme, authority: nil, path: path, query: query, fragment: fragment) 334 | } 335 | 336 | /// Creates a filename wrapper from URI components. 337 | /// 338 | /// Related SQLite: "URI filenames", `sqlite3_open_v2`, `SQLITE_OPEN_URI` 339 | @inlinable public static func uri( 340 | scheme: URI.Scheme = .file, 341 | authority: URI.Authority, 342 | path: URI.Path? = nil, 343 | query: URI.Query? = nil, 344 | fragment: URI.Fragment? = nil 345 | ) -> Self { 346 | Self._uri(scheme: scheme, authority: authority, path: path, query: query, fragment: fragment) 347 | } 348 | 349 | @usableFromInline 350 | static func _uri( 351 | scheme: URI.Scheme, 352 | authority: URI.Authority?, 353 | path: URI.Path?, 354 | query: URI.Query?, 355 | fragment: URI.Fragment? 356 | ) -> Self { 357 | var result = "" 358 | result.reserveCapacity(32) 359 | 360 | result.append(scheme.rawValue) 361 | result.append(":") 362 | 363 | if let authority { 364 | result.append("//") 365 | result.append(authority.rawValue) 366 | if let path { 367 | result.append(path.requiringLeadingSlash().rawValue) 368 | } 369 | } else if let path { 370 | result.append(path.rawValue) 371 | } 372 | 373 | if let query { 374 | result.append("?") 375 | result.append(query.rawValue) 376 | } 377 | 378 | if let fragment { 379 | result.append("#") 380 | result.append(fragment.rawValue) 381 | } 382 | 383 | return Self(rawValue: result) 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /Sources/LSQLite/ResultCode.swift: -------------------------------------------------------------------------------- 1 | import MissedSwiftSQLite 2 | 3 | /// Strongly typed wrapper around SQLite primary and extended result codes returned by API calls. 4 | /// 5 | /// Related SQLite: `sqlite3_errcode`, `sqlite3_extended_errcode`, `sqlite3_errstr`, `SQLITE_OK`, `SQLITE_BUSY`, `SQLITE_IOERR_*` 6 | @frozen public struct ResultCode: Equatable, RawRepresentable, CustomDebugStringConvertible { 7 | public let rawValue: Int32 8 | 9 | @inlinable public init(rawValue: Int32) { 10 | self.rawValue = rawValue 11 | } 12 | 13 | /// Successful result 14 | public static let ok = Self(rawValue: SQLITE_OK) 15 | /// Generic error 16 | public static let error = Self(rawValue: SQLITE_ERROR) 17 | /// Internal logic error in SQLite 18 | public static let `internal` = Self(rawValue: SQLITE_INTERNAL) 19 | /// Access permission denied 20 | public static let permission = Self(rawValue: SQLITE_PERM) 21 | /// Callback routine requested an abort 22 | public static let abort = Self(rawValue: SQLITE_ABORT) 23 | /// The database file is locked 24 | public static let busy = Self(rawValue: SQLITE_BUSY) 25 | /// A table in the database is locked 26 | public static let locked = Self(rawValue: SQLITE_LOCKED) 27 | /// A malloc() failed 28 | public static let noMemory = Self(rawValue: SQLITE_NOMEM) 29 | /// Attempt to write a readonly database 30 | public static let readonly = Self(rawValue: SQLITE_READONLY) 31 | /// Operation terminated by sqlite3_interrupt() 32 | public static let interrupt = Self(rawValue: SQLITE_INTERRUPT) 33 | /// Some kind of disk I/O error occurred 34 | public static let ioError = Self(rawValue: SQLITE_IOERR) 35 | /// The database disk image is malformed 36 | public static let corrupt = Self(rawValue: SQLITE_CORRUPT) 37 | /// Unknown opcode in sqlite3_file_control() 38 | public static let notFound = Self(rawValue: SQLITE_NOTFOUND) 39 | /// Insertion failed because database is full 40 | public static let full = Self(rawValue: SQLITE_FULL) 41 | /// Unable to open the database file 42 | public static let cantOpen = Self(rawValue: SQLITE_CANTOPEN) 43 | /// Database lock protocol error 44 | public static let `protocol` = Self(rawValue: SQLITE_PROTOCOL) 45 | /// Internal use only 46 | public static let empty = Self(rawValue: SQLITE_EMPTY) 47 | /// The database schema changed 48 | public static let schema = Self(rawValue: SQLITE_SCHEMA) 49 | /// String or BLOB exceeds size limit 50 | public static let tooBig = Self(rawValue: SQLITE_TOOBIG) 51 | /// Abort due to constraint violation 52 | public static let constraint = Self(rawValue: SQLITE_CONSTRAINT) 53 | /// Data type mismatch 54 | public static let mismatch = Self(rawValue: SQLITE_MISMATCH) 55 | /// Library used incorrectly 56 | public static let misuse = Self(rawValue: SQLITE_MISUSE) 57 | /// Uses OS features not supported on host 58 | public static let noLFS = Self(rawValue: SQLITE_NOLFS) 59 | /// Authorization denied 60 | public static let auth = Self(rawValue: SQLITE_AUTH) 61 | /// Not used 62 | public static let format = Self(rawValue: SQLITE_FORMAT) 63 | /// 2nd parameter to sqlite3_bind out of range 64 | public static let range = Self(rawValue: SQLITE_RANGE) 65 | /// File opened that is not a database file 66 | public static let notADB = Self(rawValue: SQLITE_NOTADB) 67 | /// Notifications from sqlite3_log() 68 | public static let notice = Self(rawValue: SQLITE_NOTICE) 69 | /// Warnings from sqlite3_log() 70 | public static let warning = Self(rawValue: SQLITE_WARNING) 71 | /// sqlite3_step() has another row ready 72 | public static let row = Self(rawValue: SQLITE_ROW) 73 | /// sqlite3_step() has finished executing 74 | public static let done = Self(rawValue: SQLITE_DONE) 75 | 76 | public static let errorMissingCollSeq = Self(rawValue: LSQLITE_ERROR_MISSING_COLLSEQ) 77 | public static let errorRetry = Self(rawValue: LSQLITE_ERROR_RETRY) 78 | public static let errorSnapshot = Self(rawValue: LSQLITE_ERROR_SNAPSHOT) 79 | public static let ioErrorRead = Self(rawValue: LSQLITE_IOERR_READ) 80 | public static let ioErrorShortRead = Self(rawValue: LSQLITE_IOERR_SHORT_READ) 81 | public static let ioErrorWrite = Self(rawValue: LSQLITE_IOERR_WRITE) 82 | public static let ioErrorFSync = Self(rawValue: LSQLITE_IOERR_FSYNC) 83 | public static let ioErrorDirFSync = Self(rawValue: LSQLITE_IOERR_DIR_FSYNC) 84 | public static let ioErrorTruncate = Self(rawValue: LSQLITE_IOERR_TRUNCATE) 85 | public static let ioErrorFStat = Self(rawValue: LSQLITE_IOERR_FSTAT) 86 | public static let ioErrorUnlock = Self(rawValue: LSQLITE_IOERR_UNLOCK) 87 | public static let ioErrorRDLock = Self(rawValue: LSQLITE_IOERR_RDLOCK) 88 | public static let ioErrorDelete = Self(rawValue: LSQLITE_IOERR_DELETE) 89 | public static let ioErrorBlocked = Self(rawValue: LSQLITE_IOERR_BLOCKED) 90 | public static let ioErrorNoMem = Self(rawValue: LSQLITE_IOERR_NOMEM) 91 | public static let ioErrorAccess = Self(rawValue: LSQLITE_IOERR_ACCESS) 92 | public static let ioErrorCheckReservedLock = Self(rawValue: LSQLITE_IOERR_CHECKRESERVEDLOCK) 93 | public static let ioErrorLock = Self(rawValue: LSQLITE_IOERR_LOCK) 94 | public static let ioErrorClose = Self(rawValue: LSQLITE_IOERR_CLOSE) 95 | public static let ioErrorDirClose = Self(rawValue: LSQLITE_IOERR_DIR_CLOSE) 96 | public static let ioErrorShMOpen = Self(rawValue: LSQLITE_IOERR_SHMOPEN) 97 | public static let ioErrorShMSize = Self(rawValue: LSQLITE_IOERR_SHMSIZE) 98 | public static let ioErrorShMLock = Self(rawValue: LSQLITE_IOERR_SHMLOCK) 99 | public static let ioErrorShMMap = Self(rawValue: LSQLITE_IOERR_SHMMAP) 100 | public static let ioErrorSeek = Self(rawValue: LSQLITE_IOERR_SEEK) 101 | public static let ioErrorDeleteNoEnt = Self(rawValue: LSQLITE_IOERR_DELETE_NOENT) 102 | public static let ioErrorMMap = Self(rawValue: LSQLITE_IOERR_MMAP) 103 | public static let ioErrorGetTempPath = Self(rawValue: LSQLITE_IOERR_GETTEMPPATH) 104 | public static let ioErrorConvPath = Self(rawValue: LSQLITE_IOERR_CONVPATH) 105 | public static let ioErrorVNode = Self(rawValue: LSQLITE_IOERR_VNODE) 106 | public static let ioErrorAuth = Self(rawValue: LSQLITE_IOERR_AUTH) 107 | public static let ioErrorBeginAtomic = Self(rawValue: LSQLITE_IOERR_BEGIN_ATOMIC) 108 | public static let ioErrorCommitAtomic = Self(rawValue: LSQLITE_IOERR_COMMIT_ATOMIC) 109 | public static let ioErrorRollbackAtomic = Self(rawValue: LSQLITE_IOERR_ROLLBACK_ATOMIC) 110 | public static let lockedSharedCache = Self(rawValue: LSQLITE_LOCKED_SHAREDCACHE) 111 | public static let lockedVTab = Self(rawValue: LSQLITE_LOCKED_VTAB) 112 | public static let busyRecovery = Self(rawValue: LSQLITE_BUSY_RECOVERY) 113 | public static let busySnapshot = Self(rawValue: LSQLITE_BUSY_SNAPSHOT) 114 | public static let cantOpenNoTempDir = Self(rawValue: LSQLITE_CANTOPEN_NOTEMPDIR) 115 | public static let cantOpenIsDir = Self(rawValue: LSQLITE_CANTOPEN_ISDIR) 116 | public static let cantOpenFullPath = Self(rawValue: LSQLITE_CANTOPEN_FULLPATH) 117 | public static let cantOpenConvPath = Self(rawValue: LSQLITE_CANTOPEN_CONVPATH) 118 | /// Not Used 119 | public static let cantOpenDirtyWAL = Self(rawValue: LSQLITE_CANTOPEN_DIRTYWAL) 120 | public static let corruptVTab = Self(rawValue: LSQLITE_CORRUPT_VTAB) 121 | public static let corruptSequence = Self(rawValue: LSQLITE_CORRUPT_SEQUENCE) 122 | public static let readonlyRecovery = Self(rawValue: LSQLITE_READONLY_RECOVERY) 123 | public static let readonlyCantLock = Self(rawValue: LSQLITE_READONLY_CANTLOCK) 124 | public static let readonlyRollback = Self(rawValue: LSQLITE_READONLY_ROLLBACK) 125 | public static let readonlyDBMoved = Self(rawValue: LSQLITE_READONLY_DBMOVED) 126 | public static let readonlyCantInit = Self(rawValue: LSQLITE_READONLY_CANTINIT) 127 | public static let readonlyDirectory = Self(rawValue: LSQLITE_READONLY_DIRECTORY) 128 | public static let abortRollback = Self(rawValue: LSQLITE_ABORT_ROLLBACK) 129 | public static let constraintCheck = Self(rawValue: LSQLITE_CONSTRAINT_CHECK) 130 | public static let constraintCommitHook = Self(rawValue: LSQLITE_CONSTRAINT_COMMITHOOK) 131 | public static let constraintForeignKey = Self(rawValue: LSQLITE_CONSTRAINT_FOREIGNKEY) 132 | public static let constraintFunction = Self(rawValue: LSQLITE_CONSTRAINT_FUNCTION) 133 | public static let constraintNotNull = Self(rawValue: LSQLITE_CONSTRAINT_NOTNULL) 134 | public static let constraintPrimaryKey = Self(rawValue: LSQLITE_CONSTRAINT_PRIMARYKEY) 135 | public static let constraintTrigger = Self(rawValue: LSQLITE_CONSTRAINT_TRIGGER) 136 | public static let constraintUnique = Self(rawValue: LSQLITE_CONSTRAINT_UNIQUE) 137 | public static let constraintVTab = Self(rawValue: LSQLITE_CONSTRAINT_VTAB) 138 | public static let constraintRowID = Self(rawValue: LSQLITE_CONSTRAINT_ROWID) 139 | public static let noticeRecoverWAL = Self(rawValue: LSQLITE_NOTICE_RECOVER_WAL) 140 | public static let noticeRecoverRollback = Self(rawValue: LSQLITE_NOTICE_RECOVER_ROLLBACK) 141 | public static let warningAutoIndex = Self(rawValue: LSQLITE_WARNING_AUTOINDEX) 142 | public static let authUser = Self(rawValue: LSQLITE_AUTH_USER) 143 | public static let okLoadPermanently = Self(rawValue: LSQLITE_OK_LOAD_PERMANENTLY) 144 | 145 | /// Human-readable description for this result code. 146 | /// 147 | /// Related SQLite: `sqlite3_errstr`, `sqlite3_errcode`, `sqlite3_extended_errcode` 148 | @available(iOS 8.2, macOS 10.10, tvOS 8.2, watchOS 2.0, *) 149 | @inlinable public var errorString: UnsafePointer { 150 | sqlite3_errstr(rawValue) 151 | } 152 | 153 | /// Debug-friendly constant name for this result code. 154 | /// 155 | /// Related SQLite: `sqlite3_errcode`, `sqlite3_extended_errcode`, `SQLITE_*` 156 | public var debugDescription: String { 157 | switch self { 158 | case .ok: return "SQLITE_OK" 159 | case .error: return "SQLITE_ERROR" 160 | case .internal: return "SQLITE_INTERNAL" 161 | case .permission: return "SQLITE_PERM" 162 | case .abort: return "SQLITE_ABORT" 163 | case .busy: return "SQLITE_BUSY" 164 | case .locked: return "SQLITE_LOCKED" 165 | case .noMemory: return "SQLITE_NOMEM" 166 | case .readonly: return "SQLITE_READONLY" 167 | case .interrupt: return "SQLITE_INTERRUPT" 168 | case .ioError: return "SQLITE_IOERR" 169 | case .corrupt: return "SQLITE_CORRUPT" 170 | case .notFound: return "SQLITE_NOTFOUND" 171 | case .full: return "SQLITE_FULL" 172 | case .cantOpen: return "SQLITE_CANTOPEN" 173 | case .protocol: return "SQLITE_PROTOCOL" 174 | case .empty: return "SQLITE_EMPTY" 175 | case .schema: return "SQLITE_SCHEMA" 176 | case .tooBig: return "SQLITE_TOOBIG" 177 | case .constraint: return "SQLITE_CONSTRAINT" 178 | case .mismatch: return "SQLITE_MISMATCH" 179 | case .misuse: return "SQLITE_MISUSE" 180 | case .noLFS: return "SQLITE_NOLFS" 181 | case .auth: return "SQLITE_AUTH" 182 | case .format: return "SQLITE_FORMAT" 183 | case .range: return "SQLITE_RANGE" 184 | case .notADB: return "SQLITE_NOTADB" 185 | case .notice: return "SQLITE_NOTICE" 186 | case .warning: return "SQLITE_WARNING" 187 | case .row: return "SQLITE_ROW" 188 | case .done: return "SQLITE_DONE" 189 | 190 | case .errorMissingCollSeq: return "SQLITE_ERROR_MISSING_COLLSEQ" 191 | case .errorRetry: return "SQLITE_ERROR_RETRY" 192 | case .errorSnapshot: return "SQLITE_ERROR_SNAPSHOT" 193 | case .ioErrorRead: return "SQLITE_IOERR_READ" 194 | case .ioErrorShortRead: return "SQLITE_IOERR_SHORT_READ" 195 | case .ioErrorWrite: return "SQLITE_IOERR_WRITE" 196 | case .ioErrorFSync: return "SQLITE_IOERR_FSYNC" 197 | case .ioErrorDirFSync: return "SQLITE_IOERR_DIR_FSYNC" 198 | case .ioErrorTruncate: return "SQLITE_IOERR_TRUNCATE" 199 | case .ioErrorFStat: return "SQLITE_IOERR_FSTAT" 200 | case .ioErrorUnlock: return "SQLITE_IOERR_UNLOCK" 201 | case .ioErrorRDLock: return "SQLITE_IOERR_RDLOCK" 202 | case .ioErrorDelete: return "SQLITE_IOERR_DELETE" 203 | case .ioErrorBlocked: return "SQLITE_IOERR_BLOCKED" 204 | case .ioErrorNoMem: return "SQLITE_IOERR_NOMEM" 205 | case .ioErrorAccess: return "SQLITE_IOERR_ACCESS" 206 | case .ioErrorCheckReservedLock: return "SQLITE_IOERR_CHECKRESERVEDLOCK" 207 | case .ioErrorLock: return "SQLITE_IOERR_LOCK" 208 | case .ioErrorClose: return "SQLITE_IOERR_CLOSE" 209 | case .ioErrorDirClose: return "SQLITE_IOERR_DIR_CLOSE" 210 | case .ioErrorShMOpen: return "SQLITE_IOERR_SHMOPEN" 211 | case .ioErrorShMSize: return "SQLITE_IOERR_SHMSIZE" 212 | case .ioErrorShMLock: return "SQLITE_IOERR_SHMLOCK" 213 | case .ioErrorShMMap: return "SQLITE_IOERR_SHMMAP" 214 | case .ioErrorSeek: return "SQLITE_IOERR_SEEK" 215 | case .ioErrorDeleteNoEnt: return "SQLITE_IOERR_DELETE_NOENT" 216 | case .ioErrorMMap: return "SQLITE_IOERR_MMAP" 217 | case .ioErrorGetTempPath: return "SQLITE_IOERR_GETTEMPPATH" 218 | case .ioErrorConvPath: return "SQLITE_IOERR_CONVPATH" 219 | case .ioErrorVNode: return "SQLITE_IOERR_VNODE" 220 | case .ioErrorAuth: return "SQLITE_IOERR_AUTH" 221 | case .ioErrorBeginAtomic: return "SQLITE_IOERR_BEGIN_ATOMIC" 222 | case .ioErrorCommitAtomic: return "SQLITE_IOERR_COMMIT_ATOMIC" 223 | case .ioErrorRollbackAtomic: return "SQLITE_IOERR_ROLLBACK_ATOMIC" 224 | case .lockedSharedCache: return "SQLITE_LOCKED_SHAREDCACHE" 225 | case .lockedVTab: return "SQLITE_LOCKED_VTAB" 226 | case .busyRecovery: return "SQLITE_BUSY_RECOVERY" 227 | case .busySnapshot: return "SQLITE_BUSY_SNAPSHOT" 228 | case .cantOpenNoTempDir: return "SQLITE_CANTOPEN_NOTEMPDIR" 229 | case .cantOpenIsDir: return "SQLITE_CANTOPEN_ISDIR" 230 | case .cantOpenFullPath: return "SQLITE_CANTOPEN_FULLPATH" 231 | case .cantOpenConvPath: return "SQLITE_CANTOPEN_CONVPATH" 232 | case .cantOpenDirtyWAL: return "SQLITE_CANTOPEN_DIRTYWAL" 233 | case .corruptVTab: return "SQLITE_CORRUPT_VTAB" 234 | case .corruptSequence: return "SQLITE_CORRUPT_SEQUENCE" 235 | case .readonlyRecovery: return "SQLITE_READONLY_RECOVERY" 236 | case .readonlyCantLock: return "SQLITE_READONLY_CANTLOCK" 237 | case .readonlyRollback: return "SQLITE_READONLY_ROLLBACK" 238 | case .readonlyDBMoved: return "SQLITE_READONLY_DBMOVED" 239 | case .readonlyCantInit: return "SQLITE_READONLY_CANTINIT" 240 | case .readonlyDirectory: return "SQLITE_READONLY_DIRECTORY" 241 | case .abortRollback: return "SQLITE_ABORT_ROLLBACK" 242 | case .constraintCheck: return "SQLITE_CONSTRAINT_CHECK" 243 | case .constraintCommitHook: return "SQLITE_CONSTRAINT_COMMITHOOK" 244 | case .constraintForeignKey: return "SQLITE_CONSTRAINT_FOREIGNKEY" 245 | case .constraintFunction: return "SQLITE_CONSTRAINT_FUNCTION" 246 | case .constraintNotNull: return "SQLITE_CONSTRAINT_NOTNULL" 247 | case .constraintPrimaryKey: return "SQLITE_CONSTRAINT_PRIMARYKEY" 248 | case .constraintTrigger: return "SQLITE_CONSTRAINT_TRIGGER" 249 | case .constraintUnique: return "SQLITE_CONSTRAINT_UNIQUE" 250 | case .constraintVTab: return "SQLITE_CONSTRAINT_VTAB" 251 | case .constraintRowID: return "SQLITE_CONSTRAINT_ROWID" 252 | case .noticeRecoverWAL: return "SQLITE_NOTICE_RECOVER_WAL" 253 | case .noticeRecoverRollback: return "SQLITE_NOTICE_RECOVER_ROLLBACK" 254 | case .warningAutoIndex: return "SQLITE_WARNING_AUTOINDEX" 255 | case .authUser: return "SQLITE_AUTH_USER" 256 | case .okLoadPermanently: return "SQLITE_OK_LOAD_PERMANENTLY" 257 | default: return "ResultCode(rawValue: \(rawValue))" 258 | } 259 | } 260 | } 261 | 262 | extension Int32 { 263 | @usableFromInline var resultCode: ResultCode { 264 | .init(rawValue: self) 265 | } 266 | } 267 | --------------------------------------------------------------------------------