From c0645b50ea5cb7a4e17cf946e310aabe341a1292 Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Mon, 29 Dec 2025 21:51:39 +0200 Subject: [PATCH] Rename Database to Connection --- README.md | 14 +- Sources/LSQLite/Blob/Blob.swift | 2 +- .../Connection+Authorizer.swift} | 2 +- .../Connection+Autocommit.swift} | 2 +- .../Connection+Blob.swift} | 2 +- .../Connection+Busy.swift} | 2 +- .../Connection+Changes.swift} | 2 +- .../Connection+Checkpoint.swift} | 2 +- .../Connection+Close.swift} | 2 +- .../Connection+Collation.swift} | 4 +- .../Connection+Error.swift} | 2 +- .../Connection+Exec.swift} | 2 +- .../Connection+Filename.swift} | 2 +- .../Connection+Function.swift} | 2 +- .../Connection+Hooks.swift} | 4 +- .../Connection+Interrupt.swift} | 2 +- .../Connection+LastInsertRowid.swift} | 2 +- .../Connection+Limit.swift} | 4 +- .../Connection+Open.swift} | 16 +-- .../Connection+ProgressHandler.swift} | 2 +- .../Connection+Readonly.swift} | 2 +- .../Connection+Statement.swift} | 2 +- .../Connection+Trace.swift} | 2 +- .../Connection.swift} | 2 +- ...atabase.swift => Context+Connection.swift} | 4 +- ...abase.swift => Statement+Connection.swift} | 4 +- .../LSQLite/Statement/Statement+Prepare.swift | 24 ++-- Sources/LSQLite/Value/Value+Introspect.swift | 2 +- .../LSQLiteTests/Blob/Blob+AccessTests.swift | 30 +++-- .../Blob/Blob+LifecycleTests.swift | 48 +++---- .../Connection+AuthorizerTests.swift | 120 +++++++++++++++++ .../Connection+AutocommitTests.swift | 31 +++++ .../Connection/Connection+BlobTests.swift | 38 ++++++ .../Connection/Connection+BusyTests.swift | 47 +++++++ .../Connection/Connection+ChangesTests.swift | 32 +++++ .../Connection+CheckpointTests.swift | 58 ++++++++ .../Connection/Connection+CloseTests.swift | 26 ++++ .../Connection+CollationTests.swift | 84 ++++++++++++ .../Connection/Connection+ErrorTests.swift | 51 +++++++ .../Connection/Connection+ExecTests.swift | 68 ++++++++++ .../Connection/Connection+FilenameTests.swift | 35 +++++ .../Connection/Connection+FunctionTests.swift | 124 ++++++++++++++++++ .../Connection+HooksTests.swift} | 64 ++++----- .../Connection+InterruptTests.swift | 16 +++ .../Connection+LastInsertRowidTests.swift | 32 +++++ .../Connection/Connection+LimitTests.swift | 71 ++++++++++ .../Connection/Connection+OpenTests.swift | 87 ++++++++++++ .../Connection+ProgressHandlerTests.swift | 46 +++++++ .../Connection/Connection+ReadonlyTests.swift | 43 ++++++ .../Connection+StatementTests.swift} | 27 ++-- .../Connection+TraceTests.swift} | 58 ++++---- .../ConnectionTests.swift} | 8 +- .../Context/Context+AggregateTests.swift | 18 +-- .../Context/Context+AuxiliaryTests.swift | 14 +- .../Context/Context+ConnectionTests.swift | 43 ++++++ .../Context/Context+DatabaseTests.swift | 41 ------ .../Context/Context+ResultTests.swift | 73 ++++++----- .../Context/Context+UserDataTests.swift | 14 +- .../Database/Database+AuthorizerTests.swift | 118 ----------------- .../Database/Database+AutocommitTests.swift | 28 ---- .../Database/Database+BlobTests.swift | 36 ----- .../Database/Database+BusyTests.swift | 44 ------- .../Database/Database+ChangesTests.swift | 30 ----- .../Database/Database+CheckpointTests.swift | 55 -------- .../Database/Database+CloseTests.swift | 22 ---- .../Database/Database+CollationTests.swift | 81 ------------ .../Database/Database+ErrorTests.swift | 49 ------- .../Database/Database+ExecTests.swift | 66 ---------- .../Database/Database+FilenameTests.swift | 31 ----- .../Database/Database+FunctionTests.swift | 121 ----------------- .../Database/Database+InterruptTests.swift | 14 -- .../Database+LastInsertRowidTests.swift | 30 ----- .../Database/Database+LimitTests.swift | 68 ---------- .../Database/Database+OpenTests.swift | 85 ------------ .../Database+ProgressHandlerTests.swift | 43 ------ .../Database/Database+ReadonlyTests.swift | 40 ------ .../Statement/Statement+BindTests.swift | 22 ++-- .../Statement/Statement+BusyTests.swift | 15 ++- .../Statement/Statement+ColumnTests.swift | 28 ++-- .../Statement/Statement+ConnectionTests.swift | 21 +++ .../Statement/Statement+DatabaseTests.swift | 19 --- .../Statement/Statement+FinalizeTests.swift | 12 +- .../Statement/Statement+PrepareTests.swift | 19 +-- .../Statement/Statement+ReadonlyTests.swift | 16 ++- .../Statement/Statement+ResetTests.swift | 12 +- .../Statement/Statement+SQLTests.swift | 24 ++-- .../Statement/Statement+StepTests.swift | 12 +- .../Value/Value+GettersTests.swift | 28 ++-- .../Value/Value+IntrospectTests.swift | 14 +- .../Value/Value+MemoryTests.swift | 14 +- 90 files changed, 1440 insertions(+), 1338 deletions(-) rename Sources/LSQLite/{Database/Database+Authorizer.swift => Connection/Connection+Authorizer.swift} (99%) rename Sources/LSQLite/{Database/Database+Autocommit.swift => Connection/Connection+Autocommit.swift} (96%) rename Sources/LSQLite/{Database/Database+Blob.swift => Connection/Connection+Blob.swift} (99%) rename Sources/LSQLite/{Database/Database+Busy.swift => Connection/Connection+Busy.swift} (99%) rename Sources/LSQLite/{Database/Database+Changes.swift => Connection/Connection+Changes.swift} (98%) rename Sources/LSQLite/{Database/Database+Checkpoint.swift => Connection/Connection+Checkpoint.swift} (99%) rename Sources/LSQLite/{Database/Database+Close.swift => Connection/Connection+Close.swift} (98%) rename Sources/LSQLite/{Database/Database+Collation.swift => Connection/Connection+Collation.swift} (96%) rename Sources/LSQLite/{Database/Database+Error.swift => Connection/Connection+Error.swift} (99%) rename Sources/LSQLite/{Database/Database+Exec.swift => Connection/Connection+Exec.swift} (99%) rename Sources/LSQLite/{Database/Database+Filename.swift => Connection/Connection+Filename.swift} (96%) rename Sources/LSQLite/{Database/Database+Function.swift => Connection/Connection+Function.swift} (99%) rename Sources/LSQLite/{Database/Database+Hooks.swift => Connection/Connection+Hooks.swift} (97%) rename Sources/LSQLite/{Database/Database+Interrupt.swift => Connection/Connection+Interrupt.swift} (97%) rename Sources/LSQLite/{Database/Database+LastInsertRowid.swift => Connection/Connection+LastInsertRowid.swift} (98%) rename Sources/LSQLite/{Database/Database+Limit.swift => Connection/Connection+Limit.swift} (97%) rename Sources/LSQLite/{Database/Database+Open.swift => Connection/Connection+Open.swift} (95%) rename Sources/LSQLite/{Database/Database+ProgressHandler.swift => Connection/Connection+ProgressHandler.swift} (98%) rename Sources/LSQLite/{Database/Database+Readonly.swift => Connection/Connection+Readonly.swift} (98%) rename Sources/LSQLite/{Database/Database+Statement.swift => Connection/Connection+Statement.swift} (95%) rename Sources/LSQLite/{Database/Database+Trace.swift => Connection/Connection+Trace.swift} (99%) rename Sources/LSQLite/{Database/Database.swift => Connection/Connection.swift} (90%) rename Sources/LSQLite/Context/{Context+Database.swift => Context+Connection.swift} (70%) rename Sources/LSQLite/Statement/{Statement+Database.swift => Statement+Connection.swift} (65%) create mode 100644 Tests/LSQLiteTests/Connection/Connection+AuthorizerTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+AutocommitTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+BlobTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+BusyTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+ChangesTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+CheckpointTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+CloseTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+CollationTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+ErrorTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+ExecTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+FilenameTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift rename Tests/LSQLiteTests/{Database/Database+HooksTests.swift => Connection/Connection+HooksTests.swift} (55%) create mode 100644 Tests/LSQLiteTests/Connection/Connection+InterruptTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+LastInsertRowidTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+LimitTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+OpenTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+ProgressHandlerTests.swift create mode 100644 Tests/LSQLiteTests/Connection/Connection+ReadonlyTests.swift rename Tests/LSQLiteTests/{Database/Database+StatementTests.swift => Connection/Connection+StatementTests.swift} (56%) rename Tests/LSQLiteTests/{Database/Database+TraceTests.swift => Connection/Connection+TraceTests.swift} (53%) rename Tests/LSQLiteTests/{Database/DatabaseTests.swift => Connection/ConnectionTests.swift} (52%) create mode 100644 Tests/LSQLiteTests/Context/Context+ConnectionTests.swift delete mode 100644 Tests/LSQLiteTests/Context/Context+DatabaseTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+AuthorizerTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+AutocommitTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+BlobTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+BusyTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+ChangesTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+CheckpointTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+CloseTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+CollationTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+ErrorTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+ExecTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+FilenameTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+FunctionTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+InterruptTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+LastInsertRowidTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+LimitTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+OpenTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+ProgressHandlerTests.swift delete mode 100644 Tests/LSQLiteTests/Database/Database+ReadonlyTests.swift create mode 100644 Tests/LSQLiteTests/Statement/Statement+ConnectionTests.swift delete mode 100644 Tests/LSQLiteTests/Statement/Statement+DatabaseTests.swift diff --git a/README.md b/README.md index 6ddc7fc..3504666 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The SQLite C API is small and powerful, but in Swift it comes with a few pain po - 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. LSQLite keeps the *exact* SQLite API surface and behavior, but: -- Wraps raw handles like `sqlite3 *` and `sqlite3_stmt *` into small Swift structs (`Database`, `Statement`, …). +- Wraps raw handles like `sqlite3 *` and `sqlite3_stmt *` into small Swift structs (`Connection`, `Statement`, …). - Wraps result codes and flags into typed Swift values (`ResultCode`, `OpenFlag`, …). - Leaves control flow and error handling exactly as in the C API: you still check result codes instead of catching errors. @@ -74,8 +74,8 @@ In Xcode, you can also add it via: import LSQLite // 1. Open database -var db: Database? -let openResult = Database.open(&db, at: .init(rawValue: databasePath), withOpenFlags: [.readwrite, .create]) +var db: Connection? +let openResult = Connection.open(&db, at: .init(rawValue: databasePath), withOpenFlags: [.readwrite, .create]) guard openResult == .ok, let db else { fatalError("Failed to open database: \(openResult)") } @@ -123,8 +123,8 @@ if rc != SQLITE_OK { /* handle error */ } ```swift import LSQLite -var db: Database? -let rc = Database.open(&db, at: .init(rawValue: "test.db"), withOpenFlags: [.readwrite, .create]) +var db: Connection? +let rc = Connection.open(&db, at: .init(rawValue: "test.db"), withOpenFlags: [.readwrite, .create]) if rc != .ok { /* handle error */ } ``` @@ -166,7 +166,7 @@ let rawCode: Int32 = db.close().rawValue From C to LSQLite: ```swift -let db = Database(rawValue: someSQLitePointer) +let db = Connection(rawValue: someSQLitePointer) let code = ResultCode(rawValue: SQLITE_BUSY) ``` @@ -188,7 +188,7 @@ It is a low-level, but safer and more readable, Swift presentation of the origin - **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. - **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. -- **Type safety:** Typed wrappers (`Database`, `Statement`, `ResultCode`, `OpenFlag`, etc.) make it harder to pass the wrong pointer or constant. +- **Type safety:** Typed wrappers (`Connection`, `Statement`, `ResultCode`, `OpenFlag`, etc.) make it harder to pass the wrong pointer or constant. ## Contributing diff --git a/Sources/LSQLite/Blob/Blob.swift b/Sources/LSQLite/Blob/Blob.swift index d83edaa..53d503a 100644 --- a/Sources/LSQLite/Blob/Blob.swift +++ b/Sources/LSQLite/Blob/Blob.swift @@ -1,6 +1,6 @@ /// Wrapper around an open BLOB handle for incremental I/O. /// -/// Create a handle with `Database.openBlob(_:databaseName:tableName:columnName:rowID:flags:)` +/// Create a handle with `Connection.openBlob(_:databaseName:tableName:columnName:rowID:flags:)` /// and close it when finished. Do not use the handle after closing. /// /// Related SQLite: `sqlite3_blob`, `sqlite3_blob_open`, `sqlite3_blob_close`, `sqlite3_blob_read`, `sqlite3_blob_write`, `sqlite3_blob_bytes` diff --git a/Sources/LSQLite/Database/Database+Authorizer.swift b/Sources/LSQLite/Connection/Connection+Authorizer.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Authorizer.swift rename to Sources/LSQLite/Connection/Connection+Authorizer.swift index bf3e3cf..726c742 100644 --- a/Sources/LSQLite/Database/Database+Authorizer.swift +++ b/Sources/LSQLite/Connection/Connection+Authorizer.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Authorization callback invoked while SQL statements are being compiled. /// /// The callback runs as the compiler considers each operation. Return `.ok` to allow diff --git a/Sources/LSQLite/Database/Database+Autocommit.swift b/Sources/LSQLite/Connection/Connection+Autocommit.swift similarity index 96% rename from Sources/LSQLite/Database/Database+Autocommit.swift rename to Sources/LSQLite/Connection/Connection+Autocommit.swift index def6302..713495c 100644 --- a/Sources/LSQLite/Database/Database+Autocommit.swift +++ b/Sources/LSQLite/Connection/Connection+Autocommit.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Indicates whether this connection is currently in autocommit mode (no transaction open). /// /// Autocommit is enabled by default. It is disabled by explicitly beginning a transaction diff --git a/Sources/LSQLite/Database/Database+Blob.swift b/Sources/LSQLite/Connection/Connection+Blob.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Blob.swift rename to Sources/LSQLite/Connection/Connection+Blob.swift index 38c422d..088c854 100644 --- a/Sources/LSQLite/Database/Database+Blob.swift +++ b/Sources/LSQLite/Connection/Connection+Blob.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Access flags for incremental BLOB I/O opened via `openBlob(_:databaseName:tableName:columnName:rowID:flags:)`. /// /// Use `.readonly` for read-only access or `.readwrite` for read/write access. SQLite treats any diff --git a/Sources/LSQLite/Database/Database+Busy.swift b/Sources/LSQLite/Connection/Connection+Busy.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Busy.swift rename to Sources/LSQLite/Connection/Connection+Busy.swift index 43c309c..6d80c55 100644 --- a/Sources/LSQLite/Database/Database+Busy.swift +++ b/Sources/LSQLite/Connection/Connection+Busy.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Callback invoked when SQLite reports a busy contention; return a retry decision. /// /// Related SQLite: `sqlite3_busy_handler`, `sqlite3_busy_timeout` diff --git a/Sources/LSQLite/Database/Database+Changes.swift b/Sources/LSQLite/Connection/Connection+Changes.swift similarity index 98% rename from Sources/LSQLite/Database/Database+Changes.swift rename to Sources/LSQLite/Connection/Connection+Changes.swift index d0639e4..14b714e 100644 --- a/Sources/LSQLite/Database/Database+Changes.swift +++ b/Sources/LSQLite/Connection/Connection+Changes.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Number of rows changed by the most recent INSERT, UPDATE, or DELETE on this connection. /// /// Only changes made directly by the statement are counted; changes from triggers, foreign key diff --git a/Sources/LSQLite/Database/Database+Checkpoint.swift b/Sources/LSQLite/Connection/Connection+Checkpoint.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Checkpoint.swift rename to Sources/LSQLite/Connection/Connection+Checkpoint.swift index 3b6129b..ed6c5fc 100644 --- a/Sources/LSQLite/Database/Database+Checkpoint.swift +++ b/Sources/LSQLite/Connection/Connection+Checkpoint.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// WAL checkpoint modes used by `walCheckpoint(_:mode:frameCount:totalFrameCount:)` and auto-checkpointing. /// /// Related SQLite: `SQLITE_CHECKPOINT_PASSIVE`, `SQLITE_CHECKPOINT_FULL`, `SQLITE_CHECKPOINT_RESTART`, `SQLITE_CHECKPOINT_TRUNCATE` diff --git a/Sources/LSQLite/Database/Database+Close.swift b/Sources/LSQLite/Connection/Connection+Close.swift similarity index 98% rename from Sources/LSQLite/Database/Database+Close.swift rename to Sources/LSQLite/Connection/Connection+Close.swift index 9cfdc34..03e3346 100644 --- a/Sources/LSQLite/Database/Database+Close.swift +++ b/Sources/LSQLite/Connection/Connection+Close.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Closes the connection immediately. /// /// This call fails with `.busy` if there are unfinalized statements, open BLOB handles, diff --git a/Sources/LSQLite/Database/Database+Collation.swift b/Sources/LSQLite/Connection/Connection+Collation.swift similarity index 96% rename from Sources/LSQLite/Database/Database+Collation.swift rename to Sources/LSQLite/Connection/Connection+Collation.swift index 219ee88..46c522e 100644 --- a/Sources/LSQLite/Database/Database+Collation.swift +++ b/Sources/LSQLite/Connection/Connection+Collation.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Comparator used by SQLite to order text for a custom collation. /// /// Related SQLite: `sqlite3_create_collation_v2`, `sqlite3_create_collation`, `sqlite3_create_collation16` @@ -14,7 +14,7 @@ extension Database { /// Callback used to lazily create missing collations on demand. /// /// Related SQLite: `sqlite3_collation_needed`, `sqlite3_collation_needed16` - public typealias CollationNeededHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ collationFlag: Int32, _ name: UnsafePointer?) -> Void + public typealias CollationNeededHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ connection: OpaquePointer?, _ collationFlag: Int32, _ name: UnsafePointer?) -> Void /// Text encoding and behavior flags for user-defined collations. /// diff --git a/Sources/LSQLite/Database/Database+Error.swift b/Sources/LSQLite/Connection/Connection+Error.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Error.swift rename to Sources/LSQLite/Connection/Connection+Error.swift index 1ed2afd..e9fb126 100644 --- a/Sources/LSQLite/Database/Database+Error.swift +++ b/Sources/LSQLite/Connection/Connection+Error.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Switch controlling whether extended result codes are reported for this connection. /// /// Related SQLite: `sqlite3_extended_result_codes` diff --git a/Sources/LSQLite/Database/Database+Exec.swift b/Sources/LSQLite/Connection/Connection+Exec.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Exec.swift rename to Sources/LSQLite/Connection/Connection+Exec.swift index 1265a8a..ad522c1 100644 --- a/Sources/LSQLite/Database/Database+Exec.swift +++ b/Sources/LSQLite/Connection/Connection+Exec.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Row callback invoked by `exec(_:)` when SQL produces result rows. /// /// The arrays and strings are only valid for the duration of the callback. diff --git a/Sources/LSQLite/Database/Database+Filename.swift b/Sources/LSQLite/Connection/Connection+Filename.swift similarity index 96% rename from Sources/LSQLite/Database/Database+Filename.swift rename to Sources/LSQLite/Connection/Connection+Filename.swift index e2304da..97a1175 100644 --- a/Sources/LSQLite/Database/Database+Filename.swift +++ b/Sources/LSQLite/Connection/Connection+Filename.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Returns the absolute filename for the named database on this connection. /// - Parameter name: Database name such as `"main"` or `"temp"`. /// - Returns: Absolute path, or `nil` for in-memory or temporary databases. diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Connection/Connection+Function.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Function.swift rename to Sources/LSQLite/Connection/Connection+Function.swift index b7d4d31..593e0b0 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Connection/Connection+Function.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// C callback invoked to compute a scalar SQL function result. /// /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_user_data` diff --git a/Sources/LSQLite/Database/Database+Hooks.swift b/Sources/LSQLite/Connection/Connection+Hooks.swift similarity index 97% rename from Sources/LSQLite/Database/Database+Hooks.swift rename to Sources/LSQLite/Connection/Connection+Hooks.swift index 449815f..24f8a14 100644 --- a/Sources/LSQLite/Database/Database+Hooks.swift +++ b/Sources/LSQLite/Connection/Connection+Hooks.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Commit hook invoked before the transaction is finalized; return nonzero to roll back. /// /// Related SQLite: `sqlite3_commit_hook` @@ -19,7 +19,7 @@ extension Database { /// WAL hook invoked after a write transaction commits when using WAL mode. /// /// Related SQLite: `sqlite3_wal_hook` - public typealias WALHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ databaseName: UnsafePointer?, _ pageInWALFileCount: Int32) -> Int32 + public typealias WALHookHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?, _ connection: OpaquePointer?, _ databaseName: UnsafePointer?, _ pageInWALFileCount: Int32) -> Int32 /// Return codes for commit hooks to continue or force a rollback. /// diff --git a/Sources/LSQLite/Database/Database+Interrupt.swift b/Sources/LSQLite/Connection/Connection+Interrupt.swift similarity index 97% rename from Sources/LSQLite/Database/Database+Interrupt.swift rename to Sources/LSQLite/Connection/Connection+Interrupt.swift index 890bde3..c747bcd 100644 --- a/Sources/LSQLite/Database/Database+Interrupt.swift +++ b/Sources/LSQLite/Connection/Connection+Interrupt.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Requests that all running statements on this connection abort at their earliest opportunity. /// /// It is safe to call from a different thread as long as the connection remains open. diff --git a/Sources/LSQLite/Database/Database+LastInsertRowid.swift b/Sources/LSQLite/Connection/Connection+LastInsertRowid.swift similarity index 98% rename from Sources/LSQLite/Database/Database+LastInsertRowid.swift rename to Sources/LSQLite/Connection/Connection+LastInsertRowid.swift index 789a4ce..c743d61 100644 --- a/Sources/LSQLite/Database/Database+LastInsertRowid.swift +++ b/Sources/LSQLite/Connection/Connection+LastInsertRowid.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Rowid of the most recent successful INSERT on this connection (rowid tables only). /// /// Rowid is the implicit 64-bit integer key for rowid tables and can be accessed via the diff --git a/Sources/LSQLite/Database/Database+Limit.swift b/Sources/LSQLite/Connection/Connection+Limit.swift similarity index 97% rename from Sources/LSQLite/Database/Database+Limit.swift rename to Sources/LSQLite/Connection/Connection+Limit.swift index ac5ad29..d1ce53c 100644 --- a/Sources/LSQLite/Database/Database+Limit.swift +++ b/Sources/LSQLite/Connection/Connection+Limit.swift @@ -1,7 +1,7 @@ import MissedSwiftSQLite -extension Database { - /// Runtime limit categories used with `Database.limit(for:)` and `Database.setLimit(_:for:)`. +extension Connection { + /// Runtime limit categories used with `Connection.limit(for:)` and `Connection.setLimit(_:for:)`. /// /// Related SQLite: `sqlite3_limit`, `SQLITE_LIMIT_*` @frozen public struct LimitCategory: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/LSQLite/Database/Database+Open.swift b/Sources/LSQLite/Connection/Connection+Open.swift similarity index 95% rename from Sources/LSQLite/Database/Database+Open.swift rename to Sources/LSQLite/Connection/Connection+Open.swift index f7fa41c..fb2934e 100644 --- a/Sources/LSQLite/Database/Database+Open.swift +++ b/Sources/LSQLite/Connection/Connection+Open.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Filename wrapper used when opening a connection. /// /// For `file:` URIs, pass the full URI string as `rawValue` and include `.uri` in the open flags. @@ -275,8 +275,8 @@ extension Database { /// the file is write-protected. Use `readWriteAccessState(forDatabaseNamed:)` to check the /// actual access mode. /// - /// On success, `database` is set to a new handle and the result is `.ok`. - /// On failure, `database` may still be set to a handle unless the open fails due to + /// On success, `connection` is set to a new handle and the result is `.ok`. + /// On failure, `connection` may still be set to a handle unless the open fails due to /// an out-of-memory condition. When a handle is returned, the caller is responsible /// for closing it to release resources. /// @@ -285,16 +285,16 @@ extension Database { /// case the filename is used only for cache sharing. `FileName.temporary` creates a /// private on-disk database that is deleted on close. /// - Parameters: - /// - database: Receives the connection handle; may be set even when the result is an error. + /// - connection: Receives the connection handle; may be set even when the result is an error. /// - filename: Target database path or special filename like `.memory`. /// - openFlag: Flags controlling the open mode and options. /// - Returns: Result code from the open attempt. /// /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `SQLITE_OPEN_*`, `sqlite3_temp_directory` - @inlinable public static func open(_ database: inout Database?, at filename: FileName, withOpenFlags openFlag: OpenFlag) -> ResultCode { - var databasePointer: OpaquePointer? = nil - let resultCode = sqlite3_open_v2(filename.rawValue, &databasePointer, openFlag.rawValue, nil).resultCode - database = databasePointer.map(Database.init(rawValue:)) + @inlinable public static func open(_ connection: inout Connection?, at filename: FileName, withOpenFlags openFlag: OpenFlag) -> ResultCode { + var connectionPointer: OpaquePointer? = nil + let resultCode = sqlite3_open_v2(filename.rawValue, &connectionPointer, openFlag.rawValue, nil).resultCode + connection = connectionPointer.map(Connection.init(rawValue:)) return resultCode } } diff --git a/Sources/LSQLite/Database/Database+ProgressHandler.swift b/Sources/LSQLite/Connection/Connection+ProgressHandler.swift similarity index 98% rename from Sources/LSQLite/Database/Database+ProgressHandler.swift rename to Sources/LSQLite/Connection/Connection+ProgressHandler.swift index e1a06e0..94636e3 100644 --- a/Sources/LSQLite/Database/Database+ProgressHandler.swift +++ b/Sources/LSQLite/Connection/Connection+ProgressHandler.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Callback invoked periodically during virtual machine execution; return a code indicating whether to continue. /// /// Related SQLite: `sqlite3_progress_handler` diff --git a/Sources/LSQLite/Database/Database+Readonly.swift b/Sources/LSQLite/Connection/Connection+Readonly.swift similarity index 98% rename from Sources/LSQLite/Database/Database+Readonly.swift rename to Sources/LSQLite/Connection/Connection+Readonly.swift index dcb3dc7..9e70958 100644 --- a/Sources/LSQLite/Database/Database+Readonly.swift +++ b/Sources/LSQLite/Connection/Connection+Readonly.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Read/write state values returned by `readWriteAccessState(forDatabaseNamed:)`. /// /// Related SQLite: `sqlite3_db_readonly` diff --git a/Sources/LSQLite/Database/Database+Statement.swift b/Sources/LSQLite/Connection/Connection+Statement.swift similarity index 95% rename from Sources/LSQLite/Database/Database+Statement.swift rename to Sources/LSQLite/Connection/Connection+Statement.swift index b5ccdf6..6130f65 100644 --- a/Sources/LSQLite/Database/Database+Statement.swift +++ b/Sources/LSQLite/Connection/Connection+Statement.swift @@ -1,6 +1,6 @@ import MissedSwiftSQLite -extension Database { +extension Connection { /// Returns the next prepared statement on this connection. /// - Parameter statement: The current statement, or nil to fetch the first. /// - Returns: The next statement, or nil if none exist. diff --git a/Sources/LSQLite/Database/Database+Trace.swift b/Sources/LSQLite/Connection/Connection+Trace.swift similarity index 99% rename from Sources/LSQLite/Database/Database+Trace.swift rename to Sources/LSQLite/Connection/Connection+Trace.swift index ae932e6..e0d1d49 100644 --- a/Sources/LSQLite/Database/Database+Trace.swift +++ b/Sources/LSQLite/Connection/Connection+Trace.swift @@ -1,7 +1,7 @@ import MissedSwiftSQLite @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) -extension Database { +extension Connection { /// Trace callback invoked for subscribed events with event-specific payloads. /// /// Related SQLite: `sqlite3_trace_v2` diff --git a/Sources/LSQLite/Database/Database.swift b/Sources/LSQLite/Connection/Connection.swift similarity index 90% rename from Sources/LSQLite/Database/Database.swift rename to Sources/LSQLite/Connection/Connection.swift index 9d596a1..7c0d0de 100644 --- a/Sources/LSQLite/Database/Database.swift +++ b/Sources/LSQLite/Connection/Connection.swift @@ -5,7 +5,7 @@ /// Use `rawValue` to access the underlying handle when needed. /// /// Related SQLite: `sqlite3`, `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_close`, `sqlite3_close_v2` -@frozen public struct Database: RawRepresentable { +@frozen public struct Connection: RawRepresentable { public let rawValue: OpaquePointer @inlinable public init(rawValue: OpaquePointer) { diff --git a/Sources/LSQLite/Context/Context+Database.swift b/Sources/LSQLite/Context/Context+Connection.swift similarity index 70% rename from Sources/LSQLite/Context/Context+Database.swift rename to Sources/LSQLite/Context/Context+Connection.swift index e89b43e..92dc573 100644 --- a/Sources/LSQLite/Context/Context+Database.swift +++ b/Sources/LSQLite/Context/Context+Connection.swift @@ -5,7 +5,7 @@ extension Context { /// - Returns: Database connection for the function invocation, or `nil` if unavailable. /// /// Related SQLite: `sqlite3_context_db_handle`, `sqlite3_create_function_v2` - @inlinable public var database: Database? { - return sqlite3_context_db_handle(rawValue).map(Database.init(rawValue:)) + @inlinable public var connection: Connection? { + return sqlite3_context_db_handle(rawValue).map(Connection.init(rawValue:)) } } diff --git a/Sources/LSQLite/Statement/Statement+Database.swift b/Sources/LSQLite/Statement/Statement+Connection.swift similarity index 65% rename from Sources/LSQLite/Statement/Statement+Database.swift rename to Sources/LSQLite/Statement/Statement+Connection.swift index fe3eea8..27d3829 100644 --- a/Sources/LSQLite/Statement/Statement+Database.swift +++ b/Sources/LSQLite/Statement/Statement+Connection.swift @@ -5,7 +5,7 @@ extension Statement { /// - Returns: The owning database connection, or nil if unavailable. /// /// Related SQLite: `sqlite3_db_handle` - @inlinable public var database: Database? { - return sqlite3_db_handle(rawValue).map(Database.init(rawValue:)) + @inlinable public var connection: Connection? { + return sqlite3_db_handle(rawValue).map(Connection.init(rawValue:)) } } diff --git a/Sources/LSQLite/Statement/Statement+Prepare.swift b/Sources/LSQLite/Statement/Statement+Prepare.swift index 5562c62..fef6de6 100644 --- a/Sources/LSQLite/Statement/Statement+Prepare.swift +++ b/Sources/LSQLite/Statement/Statement+Prepare.swift @@ -72,13 +72,13 @@ extension Statement { /// - statement: Receives the prepared statement, or nil if the input /// contains no SQL. /// - sql: SQL text to compile. - /// - database: Connection used to compile the statement. + /// - connection: Connection used to compile the statement. /// - Returns: Result code from compilation. /// /// Related SQLite: `sqlite3_prepare_v2` - @inlinable public static func prepare(_ statement: inout Statement?, sql: String, for database: Database) -> ResultCode { + @inlinable public static func prepare(_ statement: inout Statement?, sql: String, for connection: Connection) -> ResultCode { var tail: String? - return prepare(&statement, sql: sql, tail: &tail, for: database) + return prepare(&statement, sql: sql, tail: &tail, for: connection) } /// Compiles the first statement in a UTF-8 SQL string. @@ -87,16 +87,16 @@ extension Statement { /// contains no SQL. /// - sql: SQL text to compile. /// - tail: Receives any remaining SQL text after the first statement. - /// - database: Connection used to compile the statement. + /// - connection: Connection used to compile the statement. /// - Returns: Result code from compilation. /// /// Related SQLite: `sqlite3_prepare_v2` - @inlinable public static func prepare(_ statement: inout Statement?, sql: String, tail: inout String?, for database: Database) -> ResultCode { + @inlinable public static func prepare(_ statement: inout Statement?, sql: String, tail: inout String?, for connection: Connection) -> ResultCode { var statementPointer: OpaquePointer? var tailString: String? let resultCode = sql.withCString { cString in var tailPointer: UnsafePointer? - let result = sqlite3_prepare_v2(database.rawValue, cString, -1, &statementPointer, &tailPointer).resultCode + let result = sqlite3_prepare_v2(connection.rawValue, cString, -1, &statementPointer, &tailPointer).resultCode tailString = tailPointer.map { String(cString: $0) } return result } @@ -110,15 +110,15 @@ extension Statement { /// - statement: Receives the prepared statement, or nil if the input /// contains no SQL. /// - sql: SQL text to compile. - /// - database: Connection used to compile the statement. + /// - connection: Connection used to compile the statement. /// - prepareFlag: Options that influence compilation. /// - Returns: Result code from compilation. /// /// Related SQLite: `sqlite3_prepare_v3` @available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) - @inlinable public static func prepare(_ statement: inout Statement?, sql: String, for database: Database, prepareFlag: PrepareFlag) -> ResultCode { + @inlinable public static func prepare(_ statement: inout Statement?, sql: String, for connection: Connection, prepareFlag: PrepareFlag) -> ResultCode { var tail: String? - return prepare(&statement, sql: sql, tail: &tail, for: database, prepareFlag: prepareFlag) + return prepare(&statement, sql: sql, tail: &tail, for: connection, prepareFlag: prepareFlag) } /// Compiles the first statement in a UTF-8 SQL string with compilation options. @@ -127,18 +127,18 @@ extension Statement { /// contains no SQL. /// - sql: SQL text to compile. /// - tail: Receives any remaining SQL text after the first statement. - /// - database: Connection used to compile the statement. + /// - connection: Connection used to compile the statement. /// - prepareFlag: Options that influence compilation. /// - Returns: Result code from compilation. /// /// Related SQLite: `sqlite3_prepare_v3` @available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) - @inlinable public static func prepare(_ statement: inout Statement?, sql: String, tail: inout String?, for database: Database, prepareFlag: PrepareFlag) -> ResultCode { + @inlinable public static func prepare(_ statement: inout Statement?, sql: String, tail: inout String?, for connection: Connection, prepareFlag: PrepareFlag) -> ResultCode { var statementPointer: OpaquePointer? = nil var tailString: String? = nil let resultCode = sql.withCString { cString in var tailPointer: UnsafePointer? = nil - let result = sqlite3_prepare_v3(database.rawValue, cString, -1, prepareFlag.rawValue, &statementPointer, &tailPointer).resultCode + let result = sqlite3_prepare_v3(connection.rawValue, cString, -1, prepareFlag.rawValue, &statementPointer, &tailPointer).resultCode tailString = tailPointer.map { String(cString: $0) } return result } diff --git a/Sources/LSQLite/Value/Value+Introspect.swift b/Sources/LSQLite/Value/Value+Introspect.swift index f8902d4..e91e89f 100644 --- a/Sources/LSQLite/Value/Value+Introspect.swift +++ b/Sources/LSQLite/Value/Value+Introspect.swift @@ -68,7 +68,7 @@ extension Value { /// /// Requires a protected value; using an unprotected value is not thread-safe. /// Subtypes are preserved for SQL functions registered with - /// `Database.FunctionFlag.subtype`. + /// `Connection.FunctionFlag.subtype`. /// - Returns: Subtype tag for the value, or 0 if none is set. /// /// Related SQLite: `sqlite3_value_subtype` diff --git a/Tests/LSQLiteTests/Blob/Blob+AccessTests.swift b/Tests/LSQLiteTests/Blob/Blob+AccessTests.swift index 53b006d..fdcf030 100644 --- a/Tests/LSQLiteTests/Blob/Blob+AccessTests.swift +++ b/Tests/LSQLiteTests/Blob/Blob+AccessTests.swift @@ -3,30 +3,32 @@ import Testing @Suite("Blob+Access") final class BlobAccessTests { - private let database: Database + private let connection: Connection private let rowID: RowID private let missingRowID: RowID init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - - try #require(openDatabase.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) - try #require(openDatabase.exec("INSERT INTO blobs(data) VALUES (zeroblob(4))") == .ok) - rowID = openDatabase.lastInsertedRowID() + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + + try #require(connection.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) + try #require(connection.exec("INSERT INTO blobs(data) VALUES (zeroblob(4))") == .ok) + rowID = connection.lastInsertedRowID() missingRowID = RowID(rawValue: rowID.rawValue + 1) } deinit { - _ = database.close() + _ = connection.close() } @Test("read/write and byteCount for open blob") func readWriteAndByteCount() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readwrite) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readwrite) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } @@ -44,7 +46,7 @@ final class BlobAccessTests { @Test("read returns error when out of range") func readOutOfRangeReturnsError() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } @@ -56,7 +58,7 @@ final class BlobAccessTests { @Test("write on readonly blob returns readonly") func writeOnReadonlyBlobReturnsReadonly() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } @@ -68,7 +70,7 @@ final class BlobAccessTests { @Test("read/write on aborted blob returns abort") func readWriteOnAbortedBlobReturnsAbort() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readwrite) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readwrite) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } diff --git a/Tests/LSQLiteTests/Blob/Blob+LifecycleTests.swift b/Tests/LSQLiteTests/Blob/Blob+LifecycleTests.swift index 01c031d..0975da5 100644 --- a/Tests/LSQLiteTests/Blob/Blob+LifecycleTests.swift +++ b/Tests/LSQLiteTests/Blob/Blob+LifecycleTests.swift @@ -3,36 +3,38 @@ import Testing @Suite("Blob+Lifecycle") final class BlobLifecycleTests { - private let database: Database + private let connection: Connection private let firstRowID: RowID private let secondRowID: RowID private let missingRowID: RowID - private var didCloseDatabase = false + private var didCloseConnection = false init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - - try #require(openDatabase.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) - try #require(openDatabase.exec("INSERT INTO blobs(data) VALUES (x'010203')") == .ok) - firstRowID = openDatabase.lastInsertedRowID() - - try #require(openDatabase.exec("INSERT INTO blobs(data) VALUES (x'0A0B0C')") == .ok) - secondRowID = openDatabase.lastInsertedRowID() + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + + try #require(connection.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) + try #require(connection.exec("INSERT INTO blobs(data) VALUES (x'010203')") == .ok) + firstRowID = connection.lastInsertedRowID() + + try #require(connection.exec("INSERT INTO blobs(data) VALUES (x'0A0B0C')") == .ok) + secondRowID = connection.lastInsertedRowID() missingRowID = RowID(rawValue: secondRowID.rawValue + 1) } deinit { - guard !didCloseDatabase else { return } - _ = database.close() + guard !didCloseConnection else { return } + _ = connection.close() } @Test("reopen moves handle to another row") func reopenMovesHandleToAnotherRow() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: firstRowID, flags: .readonly) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: firstRowID, flags: .readonly) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } @@ -52,7 +54,7 @@ final class BlobLifecycleTests { @Test("reopen to missing row aborts handle") func reopenMissingRowAbortsHandle() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: secondRowID, flags: .readwrite) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: secondRowID, flags: .readwrite) #expect(openResult == .ok) let openedBlob = try #require(blob) defer { _ = openedBlob.close() } @@ -71,20 +73,20 @@ final class BlobLifecycleTests { #expect(reopenAgainResult == .abort) } - @Test("database close fails while blob is open") - func databaseCloseFailsWhileBlobIsOpen() throws { + @Test("connection close fails while blob is open") + func connectionCloseFailsWhileBlobIsOpen() throws { var blob: Blob? - let openResult = database.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: firstRowID, flags: .readonly) + let openResult = connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: firstRowID, flags: .readonly) #expect(openResult == .ok) let openedBlob = try #require(blob) - let closeWhileOpen = database.close() + let closeWhileOpen = connection.close() #expect(closeWhileOpen == .busy) _ = openedBlob.close() - let closeAfterBlob = database.close() + let closeAfterBlob = connection.close() #expect(closeAfterBlob == .ok) - didCloseDatabase = closeAfterBlob == .ok + didCloseConnection = closeAfterBlob == .ok } } diff --git a/Tests/LSQLiteTests/Connection/Connection+AuthorizerTests.swift b/Tests/LSQLiteTests/Connection/Connection+AuthorizerTests.swift new file mode 100644 index 0000000..76c9fb0 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+AuthorizerTests.swift @@ -0,0 +1,120 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Authorizer") +struct ConnectionAuthorizerTests { + @Test("AuthorizerHandlerResult init(rawValue:) preserves rawValue") + func authorizerHandlerResultRawValueRoundTrip() { + let rawValue = Int32(8) + let result = Connection.AuthorizerHandlerResult(rawValue: rawValue) + #expect(result.rawValue == rawValue) + } + + @Test("AuthorizerHandlerActionCode init(rawValue:) preserves rawValue") + func authorizerHandlerActionCodeRawValueRoundTrip() { + let rawValue = Int32(9) + let actionCode = Connection.AuthorizerHandlerActionCode(rawValue: rawValue) + #expect(actionCode.rawValue == rawValue) + } + + @Test("AuthorizerHandlerResult constants match SQLite") + func authorizerHandlerResultConstantsMatchSQLite() { + #expect(Connection.AuthorizerHandlerResult.ok.rawValue == SQLITE_OK) + #expect(Connection.AuthorizerHandlerResult.deny.rawValue == SQLITE_DENY) + #expect(Connection.AuthorizerHandlerResult.ignore.rawValue == SQLITE_IGNORE) + } + + @Test("AuthorizerHandlerActionCode constants match SQLite") + func authorizerHandlerActionCodeConstantsMatchSQLite() { + #expect(Connection.AuthorizerHandlerActionCode.createIndex.rawValue == SQLITE_CREATE_INDEX) + #expect(Connection.AuthorizerHandlerActionCode.createTable.rawValue == SQLITE_CREATE_TABLE) + #expect(Connection.AuthorizerHandlerActionCode.createTempIndex.rawValue == SQLITE_CREATE_TEMP_INDEX) + #expect(Connection.AuthorizerHandlerActionCode.createTempTable.rawValue == SQLITE_CREATE_TEMP_TABLE) + #expect(Connection.AuthorizerHandlerActionCode.createTempTrigger.rawValue == SQLITE_CREATE_TEMP_TRIGGER) + #expect(Connection.AuthorizerHandlerActionCode.createTempView.rawValue == SQLITE_CREATE_TEMP_VIEW) + #expect(Connection.AuthorizerHandlerActionCode.createTrigger.rawValue == SQLITE_CREATE_TRIGGER) + #expect(Connection.AuthorizerHandlerActionCode.createView.rawValue == SQLITE_CREATE_VIEW) + #expect(Connection.AuthorizerHandlerActionCode.delete.rawValue == SQLITE_DELETE) + #expect(Connection.AuthorizerHandlerActionCode.dropIndex.rawValue == SQLITE_DROP_INDEX) + #expect(Connection.AuthorizerHandlerActionCode.dropTable.rawValue == SQLITE_DROP_TABLE) + #expect(Connection.AuthorizerHandlerActionCode.dropTempIndex.rawValue == SQLITE_DROP_TEMP_INDEX) + #expect(Connection.AuthorizerHandlerActionCode.dropTempTable.rawValue == SQLITE_DROP_TEMP_TABLE) + #expect(Connection.AuthorizerHandlerActionCode.dropTempTrigger.rawValue == SQLITE_DROP_TEMP_TRIGGER) + #expect(Connection.AuthorizerHandlerActionCode.dropTempView.rawValue == SQLITE_DROP_TEMP_VIEW) + #expect(Connection.AuthorizerHandlerActionCode.dropTrigger.rawValue == SQLITE_DROP_TRIGGER) + #expect(Connection.AuthorizerHandlerActionCode.dropView.rawValue == SQLITE_DROP_VIEW) + #expect(Connection.AuthorizerHandlerActionCode.insert.rawValue == SQLITE_INSERT) + #expect(Connection.AuthorizerHandlerActionCode.pragma.rawValue == SQLITE_PRAGMA) + #expect(Connection.AuthorizerHandlerActionCode.read.rawValue == SQLITE_READ) + #expect(Connection.AuthorizerHandlerActionCode.select.rawValue == SQLITE_SELECT) + #expect(Connection.AuthorizerHandlerActionCode.transaction.rawValue == SQLITE_TRANSACTION) + #expect(Connection.AuthorizerHandlerActionCode.update.rawValue == SQLITE_UPDATE) + #expect(Connection.AuthorizerHandlerActionCode.attach.rawValue == SQLITE_ATTACH) + #expect(Connection.AuthorizerHandlerActionCode.detach.rawValue == SQLITE_DETACH) + #expect(Connection.AuthorizerHandlerActionCode.alterTable.rawValue == SQLITE_ALTER_TABLE) + #expect(Connection.AuthorizerHandlerActionCode.reindex.rawValue == SQLITE_REINDEX) + #expect(Connection.AuthorizerHandlerActionCode.analyze.rawValue == SQLITE_ANALYZE) + #expect(Connection.AuthorizerHandlerActionCode.createVTable.rawValue == SQLITE_CREATE_VTABLE) + #expect(Connection.AuthorizerHandlerActionCode.dropVTable.rawValue == SQLITE_DROP_VTABLE) + #expect(Connection.AuthorizerHandlerActionCode.function.rawValue == SQLITE_FUNCTION) + #expect(Connection.AuthorizerHandlerActionCode.savepoint.rawValue == SQLITE_SAVEPOINT) + #expect(Connection.AuthorizerHandlerActionCode.copy.rawValue == SQLITE_COPY) + #expect(Connection.AuthorizerHandlerActionCode.recursive.rawValue == SQLITE_RECURSIVE) + } + + @Test("AuthorizerHandlerResult descriptions map values") + func authorizerHandlerResultDescriptions() { + #expect(Connection.AuthorizerHandlerResult.ok.description == "ok") + #expect(Connection.AuthorizerHandlerResult.deny.description == "deny") + #expect(Connection.AuthorizerHandlerResult.ignore.description == "ignore") + #expect(Connection.AuthorizerHandlerResult(rawValue: 77).description == "unknown") + #expect(Connection.AuthorizerHandlerResult.ok.debugDescription == "SQLITE_OK") + #expect(Connection.AuthorizerHandlerResult(rawValue: 77).debugDescription == "77") + } + + @Test("AuthorizerHandlerActionCode descriptions map values") + func authorizerHandlerActionCodeDescriptions() { + #expect(Connection.AuthorizerHandlerActionCode.createIndex.description == "create index") + #expect(Connection.AuthorizerHandlerActionCode.dropTable.description == "drop table") + #expect(Connection.AuthorizerHandlerActionCode.select.description == "select") + #expect(Connection.AuthorizerHandlerActionCode(rawValue: -1).description == "unknown") + #expect(Connection.AuthorizerHandlerActionCode.createIndex.debugDescription == "SQLITE_CREATE_INDEX") + #expect(Connection.AuthorizerHandlerActionCode(rawValue: -1).debugDescription == "-1") + } + + @Test("setAuthorizerHandler registers callback") + func setAuthorizerHandlerRegistersCallback() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + try #require(connection.exec("CREATE TABLE auth(id INTEGER)") == .ok) + + var probe = AuthorizerProbe() + #expect(connection.setAuthorizerHandler(userData: &probe, authorizerHandler) == .ok) + + var statement: Statement? + try #require(Statement.prepare(&statement, sql: "SELECT id FROM auth", for: connection) == .ok) + let prepared = try #require(statement) + #expect(prepared.finalize() == .ok) + + #expect(probe.called) + #expect(connection.setAuthorizerHandler(userData: nil, nil) == .ok) + _ = connection.close() + } +} + +private struct AuthorizerProbe { + var called = false +} + +private func authorizerHandler(_ userData: UnsafeMutableRawPointer?, _ actionCode: Int32, _ detail1: UnsafePointer?, _ detail2: UnsafePointer?, _ databaseName: UnsafePointer?, _ triggerOrViewName: UnsafePointer?) -> Int32 { + guard let userData else { + return Connection.AuthorizerHandlerResult.ok.rawValue + } + let probe = userData.assumingMemoryBound(to: AuthorizerProbe.self) + probe.pointee.called = true + return Connection.AuthorizerHandlerResult.ok.rawValue +} diff --git a/Tests/LSQLiteTests/Connection/Connection+AutocommitTests.swift b/Tests/LSQLiteTests/Connection/Connection+AutocommitTests.swift new file mode 100644 index 0000000..9901cf8 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+AutocommitTests.swift @@ -0,0 +1,31 @@ +import LSQLite +import Testing + +@Suite("Connection+Autocommit") +final class ConnectionAutocommitTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("isAutocommit reflects transaction state") + func autocommitReflectsTransactionState() throws { + #expect(connection.isAutocommit) + + try #require(connection.exec("BEGIN") == .ok) + #expect(!connection.isAutocommit) + + try #require(connection.exec("COMMIT") == .ok) + #expect(connection.isAutocommit) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+BlobTests.swift b/Tests/LSQLiteTests/Connection/Connection+BlobTests.swift new file mode 100644 index 0000000..6dd2aa1 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+BlobTests.swift @@ -0,0 +1,38 @@ +import LSQLite +import Testing + +@Suite("Connection+Blob") +struct ConnectionOpenBlobFlagRawValueTests { + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(2) + let openBlobFlag = Connection.OpenBlobFlag(rawValue: rawValue) + #expect(openBlobFlag.rawValue == rawValue) + } + + @Test("OpenBlobFlag descriptions map values") + func openBlobFlagDescriptions() { + #expect(Connection.OpenBlobFlag.readonly.description == "readonly") + #expect(Connection.OpenBlobFlag.readwrite.description == "readwrite") + #expect(Connection.OpenBlobFlag(rawValue: 5).description == "5") + #expect(Connection.OpenBlobFlag.readonly.debugDescription == "readonly (0)") + } + + @Test("openBlob returns a blob handle") + func openBlobReturnsBlobHandle() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + try #require(connection.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) + try #require(connection.exec("INSERT INTO blobs(data) VALUES (zeroblob(2))") == .ok) + let rowID = connection.lastInsertedRowID() + + var blob: Blob? + #expect(connection.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) == .ok) + let openedBlob = try #require(blob) + #expect(openedBlob.close() == .ok) + _ = connection.close() + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+BusyTests.swift b/Tests/LSQLiteTests/Connection/Connection+BusyTests.swift new file mode 100644 index 0000000..a4a6c01 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+BusyTests.swift @@ -0,0 +1,47 @@ +import LSQLite +import Testing + +@Suite("Connection+Busy") +final class ConnectionBusyHandlerResultRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(17) + let busyHandlerResult = Connection.BusyHandlerResult(rawValue: rawValue) + #expect(busyHandlerResult.rawValue == rawValue) + } + + @Test("BusyHandlerResult descriptions map values") + func busyHandlerResultDescriptions() { + #expect(Connection.BusyHandlerResult.break.description == "break") + #expect(Connection.BusyHandlerResult.continue.description == "continue") + #expect(Connection.BusyHandlerResult(rawValue: 99).description == "unknown") + #expect(Connection.BusyHandlerResult.break.debugDescription == "break (0)") + #expect(Connection.BusyHandlerResult(rawValue: 99).debugDescription == "unknown (99)") + } + + @Test("setBusyHandler registers and clears handler") + func setBusyHandlerRegistersAndClearsHandler() { + #expect(connection.setBusyHandler(busyHandler) == .ok) + connection.setTimerBusyHandler(milliseconds: 1) + #expect(connection.setBusyHandler(userData: nil, nil) == .ok) + } +} + +private func busyHandler(_ userData: UnsafeMutableRawPointer?, _ attempt: Int32) -> Int32 { + Connection.BusyHandlerResult.break.rawValue +} diff --git a/Tests/LSQLiteTests/Connection/Connection+ChangesTests.swift b/Tests/LSQLiteTests/Connection/Connection+ChangesTests.swift new file mode 100644 index 0000000..ed58bcd --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+ChangesTests.swift @@ -0,0 +1,32 @@ +import LSQLite +import Testing + +@Suite("Connection+Changes") +final class ConnectionChangesTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec("CREATE TABLE changes(value TEXT)") == .ok) + } + + deinit { + _ = connection.close() + } + + @Test("changes and totalChanges track writes") + func changesAndTotalChangesTrackWrites() throws { + try #require(connection.exec("INSERT INTO changes(value) VALUES ('a')") == .ok) + #expect(connection.changes == 1) + let totalAfterInsert = connection.totalChanges + + try #require(connection.exec("UPDATE changes SET value = 'b'") == .ok) + #expect(connection.changes == 1) + #expect(connection.totalChanges == totalAfterInsert + 1) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+CheckpointTests.swift b/Tests/LSQLiteTests/Connection/Connection+CheckpointTests.swift new file mode 100644 index 0000000..46a3b4a --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+CheckpointTests.swift @@ -0,0 +1,58 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Checkpoint") +final class ConnectionCheckpointModeRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(9) + let mode = Connection.CheckpointMode(rawValue: rawValue) + #expect(mode.rawValue == rawValue) + } + + @Test("CheckpointMode constants match SQLite") + func checkpointModeConstantsMatchSQLite() { + #expect(Connection.CheckpointMode.passive.rawValue == SQLITE_CHECKPOINT_PASSIVE) + #expect(Connection.CheckpointMode.full.rawValue == SQLITE_CHECKPOINT_FULL) + #expect(Connection.CheckpointMode.restart.rawValue == SQLITE_CHECKPOINT_RESTART) + #expect(Connection.CheckpointMode.truncate.rawValue == SQLITE_CHECKPOINT_TRUNCATE) + } + + @Test("CheckpointMode descriptions map values") + func checkpointModeDescriptions() { + #expect(Connection.CheckpointMode.passive.description == "passive") + #expect(Connection.CheckpointMode.full.description == "full") + #expect(Connection.CheckpointMode.restart.description == "restart") + #expect(Connection.CheckpointMode.truncate.description == "truncate") + #expect(Connection.CheckpointMode(rawValue: -2).description == "unknown") + #expect(Connection.CheckpointMode.passive.debugDescription == "SQLITE_CHECKPOINT_PASSIVE") + #expect(Connection.CheckpointMode(rawValue: -2).debugDescription == "-2") + } + + @Test("autoWALCheckpoint and walCheckpoint return results") + func autoWALCheckpointAndWalCheckpointReturnResults() { + #expect(connection.autoWALCheckpoint(pageInWALFileCount: 0) == .ok) + var frameCount: Int32 = -1 + var totalFrameCount: Int32 = -1 + let result = connection.walCheckpoint("main", mode: .passive, frameCount: &frameCount, totalFrameCount: &totalFrameCount) + #expect(result == .ok) + #expect(frameCount >= -1) + #expect(totalFrameCount >= -1) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+CloseTests.swift b/Tests/LSQLiteTests/Connection/Connection+CloseTests.swift new file mode 100644 index 0000000..3ace6d8 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+CloseTests.swift @@ -0,0 +1,26 @@ +import LSQLite +import Testing + +@Suite("Connection+Close") +struct ConnectionCloseTests { + @Test("close returns ok for open connection") + func closeReturnsOk() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + #expect(connection.close() == .ok) + } + + @Test("closeV2 returns ok for open connection") + @available(iOS 8.2, macOS 10.10, tvOS 8.2, watchOS 2.0, *) + func closeV2ReturnsOk() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + #expect(connection.closeV2() == .ok) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+CollationTests.swift b/Tests/LSQLiteTests/Connection/Connection+CollationTests.swift new file mode 100644 index 0000000..9426131 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+CollationTests.swift @@ -0,0 +1,84 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Collation") +final class ConnectionCollationFlagRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(3) + let collationFlag = Connection.CollationFlag(rawValue: rawValue) + #expect(collationFlag.rawValue == rawValue) + } + + @Test("CollationFlag constants match SQLite") + func collationFlagConstantsMatchSQLite() { + #expect(Connection.CollationFlag.utf8.rawValue == SQLITE_UTF8) + #expect(Connection.CollationFlag.utf16le.rawValue == SQLITE_UTF16LE) + #expect(Connection.CollationFlag.utf16be.rawValue == SQLITE_UTF16BE) + #expect(Connection.CollationFlag.utf16.rawValue == SQLITE_UTF16) + #expect(Connection.CollationFlag.utf16Aligned.rawValue == SQLITE_UTF16_ALIGNED) + } + + @Test("CollationFlag descriptions map values") + func collationFlagDescriptions() { + #expect(Connection.CollationFlag.utf8.description == "utf8") + #expect(Connection.CollationFlag.utf16le.description == "utf16le") + #expect(Connection.CollationFlag.utf16be.description == "utf16be") + #expect(Connection.CollationFlag.utf16.description == "utf16") + #expect(Connection.CollationFlag.utf16Aligned.description == "utf16Aligned") + #expect(Connection.CollationFlag(rawValue: -2).description == "unknown") + #expect(Connection.CollationFlag.utf8.debugDescription == "SQLITE_UTF8") + #expect(Connection.CollationFlag(rawValue: -2).debugDescription == "-2") + } + + @Test("createCollation registers a comparator") + func createCollationRegistersComparator() { + #expect(connection.createCollation(name: "always_equal", flag: .utf8, compareHandler: alwaysEqualCollation) == .ok) + } + + @Test("collationNeeded registers and invokes handler") + func collationNeededRegistersAndInvokesHandler() throws { + var probe = CollationNeededProbe() + #expect(connection.collationNeeded(userData: &probe, neededHandler: collationNeededHandler) == .ok) + try #require(connection.exec("SELECT 'a' = 'b' COLLATE needs_help") == .ok) + #expect(probe.called) + #expect(connection.collationNeeded(userData: nil, neededHandler: nil) == .ok) + } +} + +private struct CollationNeededProbe { + var called = false +} + +private func alwaysEqualCollation(_ userData: UnsafeMutableRawPointer?, _ lhsLength: Int32, _ lhs: UnsafeRawPointer?, _ rhsLength: Int32, _ rhs: UnsafeRawPointer?) -> Int32 { + 0 +} + +private func collationNeededHandler(_ userData: UnsafeMutableRawPointer?, _ db: OpaquePointer?, _ collationFlag: Int32, _ name: UnsafePointer?) { + guard let userData else { + return + } + let probe = userData.assumingMemoryBound(to: CollationNeededProbe.self) + probe.pointee.called = true + guard let db, let name else { + return + } + let connection = Connection(rawValue: db) + _ = connection.createCollation(name: String(cString: name), flag: Connection.CollationFlag(rawValue: collationFlag), compareHandler: alwaysEqualCollation) +} diff --git a/Tests/LSQLiteTests/Connection/Connection+ErrorTests.swift b/Tests/LSQLiteTests/Connection/Connection+ErrorTests.swift new file mode 100644 index 0000000..ab2816d --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+ErrorTests.swift @@ -0,0 +1,51 @@ +import LSQLite +import Testing + +@Suite("Connection+Error") +final class ConnectionExtendedResultCodeStatusRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec("CREATE TABLE items(id INTEGER)") == .ok) + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(42) + let status = Connection.ExtendedResultCodeStatus(rawValue: rawValue) + #expect(status.rawValue == rawValue) + } + + @Test("ExtendedResultCodeStatus descriptions map values") + func extendedResultCodeStatusDescriptions() { + #expect(Connection.ExtendedResultCodeStatus.off.description == "off") + #expect(Connection.ExtendedResultCodeStatus.on.description == "on") + #expect(Connection.ExtendedResultCodeStatus(rawValue: 3).description == "unknown") + #expect(Connection.ExtendedResultCodeStatus.on.debugDescription == "on (1)") + #expect(Connection.ExtendedResultCodeStatus(rawValue: 3).debugDescription == "unknown (3)") + } + + @Test("last error fields update after a failure") + func lastErrorFieldsUpdateAfterFailure() throws { + #expect(connection.exec("INSERT INTO missing_table VALUES (1)") == .error) + #expect(connection.lastErrorCode == .error) + #expect(connection.lastExtendedErrorCode == .error) + #expect(!connection.lastErrorMessage.isEmpty) + } + + @Test("setExtendedResultCodes toggles status") + func setExtendedResultCodesTogglesStatus() { + #expect(connection.setExtendedResultCodes(.on) == .ok) + #expect(connection.setExtendedResultCodes(.off) == .ok) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+ExecTests.swift b/Tests/LSQLiteTests/Connection/Connection+ExecTests.swift new file mode 100644 index 0000000..f36bba5 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+ExecTests.swift @@ -0,0 +1,68 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Exec") +final class ConnectionExecCallbackResultRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec("CREATE TABLE items(id INTEGER)") == .ok) + try #require(connection.exec("INSERT INTO items(id) VALUES (1), (2)") == .ok) + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(4) + let callbackResult = Connection.ExecCallbackResult(rawValue: rawValue) + #expect(callbackResult.rawValue == rawValue) + } + + @Test("ExecCallbackResult descriptions map values") + func execCallbackResultDescriptions() { + #expect(Connection.ExecCallbackResult.continue.description == "continue") + #expect(Connection.ExecCallbackResult.abort.description == "abort") + #expect(Connection.ExecCallbackResult(rawValue: 9).description == "unknown") + #expect(Connection.ExecCallbackResult.continue.debugDescription == "continue (0)") + #expect(Connection.ExecCallbackResult(rawValue: 9).debugDescription == "unknown (9)") + } + + @Test("exec invokes callback for rows") + func execInvokesCallbackForRows() throws { + var rowCount = Int32(0) + let result = connection.exec("SELECT id FROM items ORDER BY id", userData: &rowCount, callback: execRowCounter) + #expect(result == .ok) + #expect(rowCount == 2) + } + + @Test("exec captures error messages") + func execCapturesErrorMessages() { + var errorMessage: UnsafeMutablePointer? = nil + defer { + if let errorMessage { + sqlite3_free(errorMessage) + } + } + #expect(connection.exec("SELECT * FROM missing_table", errorMessage: &errorMessage) == .error) + #expect(errorMessage != nil) + } +} + +private func execRowCounter(_ userData: UnsafeMutableRawPointer?, _ count: Int32, _ values: UnsafeMutablePointer?>?, _ columns: UnsafeMutablePointer?>?) -> Int32 { + guard let userData else { + return Connection.ExecCallbackResult.abort.rawValue + } + let rowCount = userData.assumingMemoryBound(to: Int32.self) + rowCount.pointee += 1 + return Connection.ExecCallbackResult.continue.rawValue +} diff --git a/Tests/LSQLiteTests/Connection/Connection+FilenameTests.swift b/Tests/LSQLiteTests/Connection/Connection+FilenameTests.swift new file mode 100644 index 0000000..f46ad13 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+FilenameTests.swift @@ -0,0 +1,35 @@ +import Foundation +import LSQLite +import Testing + +@Suite("Connection+Filename") +struct ConnectionFilenameTests { + @Test("filename returns empty string for in-memory connection") + func filenameReturnsEmptyStringForInMemoryConnection() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + #expect(connection.filename(forDatabaseNamed: "main") == "") + #expect(connection.filename(forDatabaseNamed: "missing") == nil) + _ = connection.close() + } + + @Test("filename returns a path for file connection") + func filenameReturnsPathForFileConnection() throws { + let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("lsqlite-filename.sqlite") + defer { + try? FileManager.default.removeItem(at: fileURL) + } + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .init(rawValue: fileURL.path), withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + let filename = connection.filename(forDatabaseNamed: "main") + #expect(filename != nil) + #expect(filename?.isEmpty == false) + _ = connection.close() + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift b/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift new file mode 100644 index 0000000..3610a42 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+FunctionTests.swift @@ -0,0 +1,124 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Function") +final class ConnectionFunctionTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("TextEncoding init(rawValue:) preserves rawValue") + func textEncodingRawValueRoundTrip() { + let rawValue = Int32(10) + let encoding = Connection.TextEncoding(rawValue: rawValue) + #expect(encoding.rawValue == rawValue) + } + + @Test("FunctionFlag init(rawValue:) preserves rawValue") + func functionFlagRawValueRoundTrip() { + let rawValue = Int32(11) + let flag = Connection.FunctionFlag(rawValue: rawValue) + #expect(flag.rawValue == rawValue) + } + + @Test("FunctionFlag init(rawValue:) preserves combined rawValue") + func functionFlagCombinedRawValueRoundTrip() { + let rawValue = Connection.FunctionFlag.deterministic.rawValue | Connection.FunctionFlag.directOnly.rawValue + let flag = Connection.FunctionFlag(rawValue: rawValue) + #expect(flag.rawValue == rawValue) + } + + @Test("TextEncoding constants match SQLite") + func textEncodingConstantsMatchSQLite() { + #expect(Connection.TextEncoding.utf8.rawValue == SQLITE_UTF8) + #expect(Connection.TextEncoding.utf16le.rawValue == SQLITE_UTF16LE) + #expect(Connection.TextEncoding.utf16be.rawValue == SQLITE_UTF16BE) + #expect(Connection.TextEncoding.utf16.rawValue == SQLITE_UTF16) + #expect(Connection.TextEncoding.any.rawValue == SQLITE_ANY) + } + + @Test("FunctionFlag constants match SQLite") + func functionFlagConstantsMatchSQLite() { + #expect(Connection.FunctionFlag.deterministic.rawValue == SQLITE_DETERMINISTIC) + #expect(Connection.FunctionFlag.directOnly.rawValue == SQLITE_DIRECTONLY) + #expect(Connection.FunctionFlag.subtype.rawValue == SQLITE_SUBTYPE) + #expect(Connection.FunctionFlag.innocuous.rawValue == SQLITE_INNOCUOUS) + #expect(Connection.FunctionFlag.resultSubtype.rawValue == SQLITE_RESULT_SUBTYPE) + } + + @Test("TextEncoding descriptions map values") + func textEncodingDescriptions() { + #expect(Connection.TextEncoding.utf8.description == "utf8") + #expect(Connection.TextEncoding.utf16le.description == "utf16le") + #expect(Connection.TextEncoding.utf16be.description == "utf16be") + #expect(Connection.TextEncoding.utf16.description == "utf16") + #expect(Connection.TextEncoding.any.description == "any") + #expect(Connection.TextEncoding(rawValue: -1).description == "unknown") + #expect(Connection.TextEncoding.utf8.debugDescription == "SQLITE_UTF8") + } + + @Test("FunctionFlag descriptions map values") + func functionFlagDescriptions() { + #expect(Connection.FunctionFlag([]).description == "[]") + #expect(Connection.FunctionFlag.deterministic.description.contains(".deterministic")) + let knownMask = UInt32(bitPattern: Connection.FunctionFlag.deterministic.rawValue) + | UInt32(bitPattern: Connection.FunctionFlag.directOnly.rawValue) + | UInt32(bitPattern: Connection.FunctionFlag.subtype.rawValue) + | UInt32(bitPattern: Connection.FunctionFlag.innocuous.rawValue) + | UInt32(bitPattern: Connection.FunctionFlag.resultSubtype.rawValue) + let unknownOnlyRaw = Int32(bitPattern: ~knownMask) + #expect(Connection.FunctionFlag(rawValue: unknownOnlyRaw).description == "unknown") + let mixed = Connection.FunctionFlag(rawValue: Connection.FunctionFlag.deterministic.rawValue | unknownOnlyRaw) + #expect(mixed.description.contains("unknown")) + #expect(Connection.FunctionFlag.deterministic.debugDescription.contains("SQLITE_DETERMINISTIC")) + #expect(mixed.debugDescription.contains("0x")) + } + + @Test("createFunction registers scalar function") + func createFunctionRegistersScalarFunction() throws { + #expect(connection.createFunction(name: "constant_seven", argumentCount: 0, textEncoding: .utf8, flags: [.deterministic], funcHandler: constantFunction) == .ok) + var statement: Statement? + try #require(Statement.prepare(&statement, sql: "SELECT constant_seven()", for: connection) == .ok) + let prepared = try #require(statement) + #expect(prepared.step() == .row) + #expect(prepared.columnInt(at: 0) == 7) + #expect(prepared.step() == .done) + #expect(prepared.finalize() == .ok) + } + + @Test("createWindowFunction registers window function") + @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) + func createWindowFunctionRegistersWindowFunction() { + #expect(connection.createWindowFunction(name: "window_zero", argumentCount: 0, textEncoding: .utf8, flags: [], stepHandler: windowStep, finalHandler: windowFinal, valueHandler: nil, inverseHandler: nil) == .ok) + } +} + +private func constantFunction(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { + guard let context else { + return + } + let contextWrapper = Context(rawValue: context) + contextWrapper.resultInt(7) +} + +private func windowStep(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { +} + +private func windowFinal(_ context: OpaquePointer?) { + guard let context else { + return + } + Context(rawValue: context).resultInt(0) +} diff --git a/Tests/LSQLiteTests/Database/Database+HooksTests.swift b/Tests/LSQLiteTests/Connection/Connection+HooksTests.swift similarity index 55% rename from Tests/LSQLiteTests/Database/Database+HooksTests.swift rename to Tests/LSQLiteTests/Connection/Connection+HooksTests.swift index effb24b..7df0147 100644 --- a/Tests/LSQLiteTests/Database/Database+HooksTests.swift +++ b/Tests/LSQLiteTests/Connection/Connection+HooksTests.swift @@ -2,58 +2,60 @@ import LSQLite import MissedSwiftSQLite import Testing -@Suite("Database+Hooks") -final class DatabaseHooksTests { - private let database: Database +@Suite("Connection+Hooks") +final class ConnectionHooksTests { + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec("CREATE TABLE hooks(id INTEGER)") == .ok) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec("CREATE TABLE hooks(id INTEGER)") == .ok) } deinit { - _ = database.close() + _ = connection.close() } @Test("CommitHookHandlerResult init(rawValue:) preserves rawValue") func commitHookHandlerResultRawValueRoundTrip() { let rawValue = Int32(6) - let result = Database.CommitHookHandlerResult(rawValue: rawValue) + let result = Connection.CommitHookHandlerResult(rawValue: rawValue) #expect(result.rawValue == rawValue) } @Test("UpdateOperation init(rawValue:) preserves rawValue") func updateOperationRawValueRoundTrip() { let rawValue = Int32(7) - let operation = Database.UpdateOperation(rawValue: rawValue) + let operation = Connection.UpdateOperation(rawValue: rawValue) #expect(operation.rawValue == rawValue) } @Test("UpdateOperation constants match SQLite") func updateOperationConstantsMatchSQLite() { - #expect(Database.UpdateOperation.delete.rawValue == SQLITE_DELETE) - #expect(Database.UpdateOperation.insert.rawValue == SQLITE_INSERT) - #expect(Database.UpdateOperation.update.rawValue == SQLITE_UPDATE) + #expect(Connection.UpdateOperation.delete.rawValue == SQLITE_DELETE) + #expect(Connection.UpdateOperation.insert.rawValue == SQLITE_INSERT) + #expect(Connection.UpdateOperation.update.rawValue == SQLITE_UPDATE) } @Test("hook result and operation descriptions map values") func hookDescriptionsMapValues() { - let commitContinue = Database.CommitHookHandlerResult.continue + let commitContinue = Connection.CommitHookHandlerResult.continue #expect(!commitContinue.description.isEmpty) #expect(commitContinue.debugDescription.contains(commitContinue.description)) - let commitUnknown = Database.CommitHookHandlerResult(rawValue: 9) + let commitUnknown = Connection.CommitHookHandlerResult(rawValue: 9) #expect(!commitUnknown.description.isEmpty) #expect(commitUnknown.debugDescription.contains("9")) - let updateDelete = Database.UpdateOperation.delete + let updateDelete = Connection.UpdateOperation.delete #expect(!updateDelete.description.isEmpty) #expect(updateDelete.debugDescription.contains("SQLITE_DELETE")) - let updateUnknown = Database.UpdateOperation(rawValue: 999) + let updateUnknown = Connection.UpdateOperation(rawValue: 999) #expect(!updateUnknown.description.isEmpty) #expect(updateUnknown.debugDescription.contains("999")) } @@ -61,21 +63,21 @@ final class DatabaseHooksTests { @Test("commit, rollback, update, and WAL hooks register and run") func hooksRegisterAndRun() throws { var probe = HookProbe() - _ = database.commitHook(&probe, commitHookHandler: commitHook) - _ = database.rollbackHook(&probe, rollbackHookHandler: rollbackHook) - _ = database.updateHook(&probe, updateHookHandler: updateHook) - _ = database.walHook(&probe, walHookHandler: walHook) + _ = connection.commitHook(&probe, commitHookHandler: commitHook) + _ = connection.rollbackHook(&probe, rollbackHookHandler: rollbackHook) + _ = connection.updateHook(&probe, updateHookHandler: updateHook) + _ = connection.walHook(&probe, walHookHandler: walHook) - try #require(database.exec("BEGIN") == .ok) - try #require(database.exec("INSERT INTO hooks(id) VALUES (1)") == .ok) - try #require(database.exec("COMMIT") == .ok) + try #require(connection.exec("BEGIN") == .ok) + try #require(connection.exec("INSERT INTO hooks(id) VALUES (1)") == .ok) + try #require(connection.exec("COMMIT") == .ok) #expect(probe.commitCalls == 1) #expect(probe.updateCalls == 1) - #expect(probe.lastUpdateOperation == Database.UpdateOperation.insert.rawValue) + #expect(probe.lastUpdateOperation == Connection.UpdateOperation.insert.rawValue) - try #require(database.exec("BEGIN") == .ok) - try #require(database.exec("ROLLBACK") == .ok) + try #require(connection.exec("BEGIN") == .ok) + try #require(connection.exec("ROLLBACK") == .ok) #expect(probe.rollbackCalls == 1) } } @@ -90,11 +92,11 @@ private struct HookProbe { private func commitHook(_ userData: UnsafeMutableRawPointer?) -> Int32 { guard let userData else { - return Database.CommitHookHandlerResult.break.rawValue + return Connection.CommitHookHandlerResult.break.rawValue } let probe = userData.assumingMemoryBound(to: HookProbe.self) probe.pointee.commitCalls += 1 - return Database.CommitHookHandlerResult.continue.rawValue + return Connection.CommitHookHandlerResult.continue.rawValue } private func rollbackHook(_ userData: UnsafeMutableRawPointer?) { @@ -114,7 +116,7 @@ private func updateHook(_ userData: UnsafeMutableRawPointer?, _ updateOperation: probe.pointee.lastUpdateOperation = updateOperation } -private func walHook(_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ databaseName: UnsafePointer?, _ pageInWALFileCount: Int32) -> Int32 { +private func walHook(_ userData: UnsafeMutableRawPointer?, _ connection: OpaquePointer?, _ databaseName: UnsafePointer?, _ pageInWALFileCount: Int32) -> Int32 { guard let userData else { return 0 } diff --git a/Tests/LSQLiteTests/Connection/Connection+InterruptTests.swift b/Tests/LSQLiteTests/Connection/Connection+InterruptTests.swift new file mode 100644 index 0000000..bd40f41 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+InterruptTests.swift @@ -0,0 +1,16 @@ +import LSQLite +import Testing + +@Suite("Connection+Interrupt") +struct ConnectionInterruptTests { + @Test("interrupt can be called on open connection") + func interruptCanBeCalledOnOpenConnection() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + connection.interrupt() + _ = connection.close() + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+LastInsertRowidTests.swift b/Tests/LSQLiteTests/Connection/Connection+LastInsertRowidTests.swift new file mode 100644 index 0000000..05c1a34 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+LastInsertRowidTests.swift @@ -0,0 +1,32 @@ +import LSQLite +import Testing + +@Suite("Connection+LastInsertRowid") +final class ConnectionLastInsertRowidTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec("CREATE TABLE items(id INTEGER PRIMARY KEY)") == .ok) + } + + deinit { + _ = connection.close() + } + + @Test("lastInsertedRowID and setLastInsertedRowID") + func lastInsertedRowIDAndSetLastInsertedRowID() throws { + try #require(connection.exec("INSERT INTO items DEFAULT VALUES") == .ok) + let inserted = connection.lastInsertedRowID() + #expect(inserted.rawValue > 0) + + let override = RowID(rawValue: 99) + connection.setLastInsertedRowID(override) + #expect(connection.lastInsertedRowID().rawValue == 99) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+LimitTests.swift b/Tests/LSQLiteTests/Connection/Connection+LimitTests.swift new file mode 100644 index 0000000..7335a1c --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+LimitTests.swift @@ -0,0 +1,71 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Limit") +final class ConnectionLimitCategoryRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(5) + let category = Connection.LimitCategory(rawValue: rawValue) + #expect(category.rawValue == rawValue) + } + + @Test("LimitCategory constants match SQLite") + func limitCategoryConstantsMatchSQLite() { + #expect(Connection.LimitCategory.length.rawValue == SQLITE_LIMIT_LENGTH) + #expect(Connection.LimitCategory.sqlLength.rawValue == SQLITE_LIMIT_SQL_LENGTH) + #expect(Connection.LimitCategory.column.rawValue == SQLITE_LIMIT_COLUMN) + #expect(Connection.LimitCategory.exprDepth.rawValue == SQLITE_LIMIT_EXPR_DEPTH) + #expect(Connection.LimitCategory.compoundSelect.rawValue == SQLITE_LIMIT_COMPOUND_SELECT) + #expect(Connection.LimitCategory.vdbeOp.rawValue == SQLITE_LIMIT_VDBE_OP) + #expect(Connection.LimitCategory.functionArg.rawValue == SQLITE_LIMIT_FUNCTION_ARG) + #expect(Connection.LimitCategory.attached.rawValue == SQLITE_LIMIT_ATTACHED) + #expect(Connection.LimitCategory.likePatternLength.rawValue == SQLITE_LIMIT_LIKE_PATTERN_LENGTH) + #expect(Connection.LimitCategory.variableNumber.rawValue == SQLITE_LIMIT_VARIABLE_NUMBER) + #expect(Connection.LimitCategory.triggerDepth.rawValue == SQLITE_LIMIT_TRIGGER_DEPTH) + #expect(Connection.LimitCategory.workerThreads.rawValue == SQLITE_LIMIT_WORKER_THREADS) + } + + @Test("LimitCategory descriptions map values") + func limitCategoryDescriptions() { + #expect(Connection.LimitCategory.length.description == "length") + #expect(Connection.LimitCategory.sqlLength.description == "sql length") + #expect(Connection.LimitCategory.column.description == "column") + #expect(Connection.LimitCategory.exprDepth.description == "expression depth") + #expect(Connection.LimitCategory.compoundSelect.description == "compound select") + #expect(Connection.LimitCategory.vdbeOp.description == "vdbe op") + #expect(Connection.LimitCategory.functionArg.description == "function arg") + #expect(Connection.LimitCategory.attached.description == "attached") + #expect(Connection.LimitCategory.likePatternLength.description == "like pattern length") + #expect(Connection.LimitCategory.variableNumber.description == "variable number") + #expect(Connection.LimitCategory.triggerDepth.description == "trigger depth") + #expect(Connection.LimitCategory.workerThreads.description == "worker threads") + #expect(Connection.LimitCategory(rawValue: -3).description == "unknown") + #expect(Connection.LimitCategory.length.debugDescription == "SQLITE_LIMIT_LENGTH") + #expect(Connection.LimitCategory(rawValue: -3).debugDescription == "-3") + } + + @Test("limit and setLimit round-trip values") + func limitAndSetLimitRoundTripValues() { + let current = connection.limit(for: .length) + let previous = connection.setLimit(current, for: .length) + #expect(previous == current) + #expect(connection.limit(for: .length) == current) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift b/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift new file mode 100644 index 0000000..ff95adc --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+OpenTests.swift @@ -0,0 +1,87 @@ +import LSQLite +import MissedSwiftSQLite +import Testing + +@Suite("Connection+Open") +struct ConnectionOpenTests { + @Test("FileName init(rawValue:) preserves rawValue") + func fileNameRawValueRoundTrip() { + let rawValue = "test.db" + let fileName = Connection.FileName(rawValue: rawValue) + #expect(fileName.rawValue == rawValue) + } + + @Test("OpenFlag init(rawValue:) preserves rawValue") + func openFlagRawValueRoundTrip() { + let rawValue = Int32(0x1200) + let openFlag = Connection.OpenFlag(rawValue: rawValue) + #expect(openFlag.rawValue == rawValue) + } + + @Test("OpenFlag init(rawValue:) preserves combined rawValue") + func openFlagCombinedRawValueRoundTrip() { + let rawValue = Connection.OpenFlag.readwrite.rawValue | Connection.OpenFlag.create.rawValue + let openFlag = Connection.OpenFlag(rawValue: rawValue) + #expect(openFlag.rawValue == rawValue) + } + + @Test("OpenFlag constants match SQLite") + func openFlagConstantsMatchSQLite() { + #expect(Connection.OpenFlag.readonly.rawValue == SQLITE_OPEN_READONLY) + #expect(Connection.OpenFlag.readwrite.rawValue == SQLITE_OPEN_READWRITE) + #expect(Connection.OpenFlag.create.rawValue == SQLITE_OPEN_CREATE) + #expect(Connection.OpenFlag.deleteOnClose.rawValue == SQLITE_OPEN_DELETEONCLOSE) + #expect(Connection.OpenFlag.exclusive.rawValue == SQLITE_OPEN_EXCLUSIVE) + #expect(Connection.OpenFlag.autoproxy.rawValue == SQLITE_OPEN_AUTOPROXY) + #expect(Connection.OpenFlag.uri.rawValue == SQLITE_OPEN_URI) + #expect(Connection.OpenFlag.memory.rawValue == SQLITE_OPEN_MEMORY) + #expect(Connection.OpenFlag.mainDB.rawValue == SQLITE_OPEN_MAIN_DB) + #expect(Connection.OpenFlag.tempDB.rawValue == SQLITE_OPEN_TEMP_DB) + #expect(Connection.OpenFlag.transientDB.rawValue == SQLITE_OPEN_TRANSIENT_DB) + #expect(Connection.OpenFlag.mainJournal.rawValue == SQLITE_OPEN_MAIN_JOURNAL) + #expect(Connection.OpenFlag.tempJournal.rawValue == SQLITE_OPEN_TEMP_JOURNAL) + #expect(Connection.OpenFlag.subjournal.rawValue == SQLITE_OPEN_SUBJOURNAL) + #expect(Connection.OpenFlag.masterJournal.rawValue == SQLITE_OPEN_MASTER_JOURNAL) + #expect(Connection.OpenFlag.noMutex.rawValue == SQLITE_OPEN_NOMUTEX) + #expect(Connection.OpenFlag.fullMutex.rawValue == SQLITE_OPEN_FULLMUTEX) + #expect(Connection.OpenFlag.sharedCache.rawValue == SQLITE_OPEN_SHAREDCACHE) + #expect(Connection.OpenFlag.privateCache.rawValue == SQLITE_OPEN_PRIVATECACHE) + #expect(Connection.OpenFlag.wal.rawValue == SQLITE_OPEN_WAL) +#if canImport(Darwin) + #expect(Connection.OpenFlag.fileProtectionComplete.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETE) + #expect(Connection.OpenFlag.fileProtectionCompleteUnlessOpen.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN) + #expect(Connection.OpenFlag.fileProtectionCompleteUntilFirstUserAuthentication.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION) + #expect(Connection.OpenFlag.fileProtectionNone.rawValue == SQLITE_OPEN_FILEPROTECTION_NONE) + #expect(Connection.OpenFlag.fileProtectionMask.rawValue == SQLITE_OPEN_FILEPROTECTION_MASK) +#endif + } + + @Test("FileName description reflects rawValue") + func fileNameDescriptionReflectsRawValue() { + #expect(Connection.FileName.memory.description == ":memory:") + #expect(Connection.FileName.temporary.description == "") + #expect(Connection.FileName(rawValue: "custom.db").description == "custom.db") + } + + @Test("OpenFlag descriptions map values") + func openFlagDescriptions() { + #expect(Connection.OpenFlag([]).description == "[]") + #expect(Connection.OpenFlag.readwrite.description.contains(".readwrite")) + #expect(Connection.OpenFlag(rawValue: 0x4000_0000).description == "unknown") + let mixed = Connection.OpenFlag(rawValue: Connection.OpenFlag.readonly.rawValue | 0x4000_0000) + #expect(mixed.description.contains("unknown")) + #expect(Connection.OpenFlag.readonly.debugDescription.contains("SQLITE_OPEN_READONLY")) + #expect(mixed.debugDescription.contains("0x")) + #expect(Connection.OpenFlag(rawValue: 0x4000_0000).debugDescription.hasPrefix("0x")) + } + + @Test("open creates an in-memory connection") + func openCreatesInMemoryConnection() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + #expect(connection.close() == .ok) + } +} diff --git a/Tests/LSQLiteTests/Connection/Connection+ProgressHandlerTests.swift b/Tests/LSQLiteTests/Connection/Connection+ProgressHandlerTests.swift new file mode 100644 index 0000000..f677cac --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+ProgressHandlerTests.swift @@ -0,0 +1,46 @@ +import LSQLite +import Testing + +@Suite("Connection+ProgressHandler") +final class ConnectionProgressHandlerResultRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(8) + let result = Connection.ProgressHandlerResult(rawValue: rawValue) + #expect(result.rawValue == rawValue) + } + + @Test("ProgressHandlerResult descriptions map values") + func progressHandlerResultDescriptions() { + #expect(Connection.ProgressHandlerResult.continue.description == "continue") + #expect(Connection.ProgressHandlerResult.interrupt.description == "interrupt") + #expect(Connection.ProgressHandlerResult(rawValue: 2).description == "unknown") + #expect(Connection.ProgressHandlerResult.continue.debugDescription == "continue (0)") + #expect(Connection.ProgressHandlerResult(rawValue: 2).debugDescription == "unknown (2)") + } + + @Test("setProgressHandler registers handler") + func setProgressHandlerRegistersHandler() { + connection.setProgressHandler(instructionCount: 10, handler: progressHandler) + connection.setProgressHandler(instructionCount: 0, handler: nil) + } +} + +private func progressHandler(_ userData: UnsafeMutableRawPointer?) -> Int32 { + Connection.ProgressHandlerResult.continue.rawValue +} diff --git a/Tests/LSQLiteTests/Connection/Connection+ReadonlyTests.swift b/Tests/LSQLiteTests/Connection/Connection+ReadonlyTests.swift new file mode 100644 index 0000000..cb0bd05 --- /dev/null +++ b/Tests/LSQLiteTests/Connection/Connection+ReadonlyTests.swift @@ -0,0 +1,43 @@ +import LSQLite +import Testing + +@Suite("Connection+Readonly") +final class ConnectionReadWriteAccessStateRawValueTests { + private let connection: Connection + + init() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + } + + deinit { + _ = connection.close() + } + + @Test("init(rawValue:) preserves rawValue") + func rawValueRoundTrip() { + let rawValue = Int32(-7) + let state = Connection.ReadWriteAccessState(rawValue: rawValue) + #expect(state.rawValue == rawValue) + } + + @Test("ReadWriteAccessState descriptions map values") + func readWriteAccessStateDescriptions() { + #expect(Connection.ReadWriteAccessState.noDatabase.description == "no database") + #expect(Connection.ReadWriteAccessState.readwrite.description == "readwrite") + #expect(Connection.ReadWriteAccessState.readonly.description == "readonly") + #expect(Connection.ReadWriteAccessState(rawValue: 9).description == "unknown") + #expect(Connection.ReadWriteAccessState.readwrite.debugDescription == "readwrite (0)") + #expect(Connection.ReadWriteAccessState(rawValue: 9).debugDescription == "unknown (9)") + } + + @Test("readWriteAccessState returns status for named connection") + func readWriteAccessStateReturnsStatusForNamedDatabase() { + #expect(connection.readWriteAccessState(forDatabaseNamed: "main") == .readwrite) + #expect(connection.readWriteAccessState(forDatabaseNamed: "missing") == .noDatabase) + } +} diff --git a/Tests/LSQLiteTests/Database/Database+StatementTests.swift b/Tests/LSQLiteTests/Connection/Connection+StatementTests.swift similarity index 56% rename from Tests/LSQLiteTests/Database/Database+StatementTests.swift rename to Tests/LSQLiteTests/Connection/Connection+StatementTests.swift index b23c3a7..97da871 100644 --- a/Tests/LSQLiteTests/Database/Database+StatementTests.swift +++ b/Tests/LSQLiteTests/Connection/Connection+StatementTests.swift @@ -1,32 +1,35 @@ import LSQLite import Testing -@Suite("Database+Statement") -final class DatabaseStatementTests { - private let database: Database +@Suite("Connection+Statement") +final class ConnectionStatementTests { + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection } deinit { - _ = database.close() + _ = connection.close() } @Test("nextStatement iterates prepared statements") func nextStatementIteratesPreparedStatements() throws { var first: Statement? var second: Statement? - try #require(Statement.prepare(&first, sql: "SELECT 1", for: database) == .ok) - try #require(Statement.prepare(&second, sql: "SELECT 2", for: database) == .ok) + try #require(Statement.prepare(&first, sql: "SELECT 1", for: connection) == .ok) + try #require(Statement.prepare(&second, sql: "SELECT 2", for: connection) == .ok) let firstStatement = try #require(first) let secondStatement = try #require(second) - let firstFromAPI = try #require(database.nextStatement(after: nil)) - let secondFromAPI = database.nextStatement(after: firstFromAPI) - let thirdFromAPI = database.nextStatement(after: secondFromAPI) + let firstFromAPI = try #require(connection.nextStatement(after: nil)) + let secondFromAPI = connection.nextStatement(after: firstFromAPI) + let thirdFromAPI = connection.nextStatement(after: secondFromAPI) let preparedPointers = [firstStatement.rawValue, secondStatement.rawValue] #expect(preparedPointers.contains(firstFromAPI.rawValue)) diff --git a/Tests/LSQLiteTests/Database/Database+TraceTests.swift b/Tests/LSQLiteTests/Connection/Connection+TraceTests.swift similarity index 53% rename from Tests/LSQLiteTests/Database/Database+TraceTests.swift rename to Tests/LSQLiteTests/Connection/Connection+TraceTests.swift index e40bbd9..b975fc2 100644 --- a/Tests/LSQLiteTests/Database/Database+TraceTests.swift +++ b/Tests/LSQLiteTests/Connection/Connection+TraceTests.swift @@ -2,15 +2,15 @@ import LSQLite import MissedSwiftSQLite import Testing -@Suite("Database+Trace") -struct DatabaseTraceTests { +@Suite("Connection+Trace") +struct ConnectionTraceTests { @Test("TraceEventCallbackResult init(rawValue:) preserves rawValue") func traceEventCallbackResultRawValueRoundTrip() { guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) else { return } let rawValue = Int32(12) - let result = Database.TraceEventCallbackResult(rawValue: rawValue) + let result = Connection.TraceEventCallbackResult(rawValue: rawValue) #expect(result.rawValue == rawValue) } @@ -20,7 +20,7 @@ struct DatabaseTraceTests { return } let rawValue = UInt32(0x20) - let code = Database.TraceEventCode(rawValue: rawValue) + let code = Connection.TraceEventCode(rawValue: rawValue) #expect(code.rawValue == rawValue) } @@ -29,8 +29,8 @@ struct DatabaseTraceTests { guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) else { return } - let rawValue = Database.TraceEventCode.statement.rawValue | Database.TraceEventCode.profile.rawValue - let code = Database.TraceEventCode(rawValue: rawValue) + let rawValue = Connection.TraceEventCode.statement.rawValue | Connection.TraceEventCode.profile.rawValue + let code = Connection.TraceEventCode(rawValue: rawValue) #expect(code.rawValue == rawValue) } @@ -39,10 +39,10 @@ struct DatabaseTraceTests { guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) else { return } - #expect(Database.TraceEventCode.statement.rawValue == UInt32(SQLITE_TRACE_STMT)) - #expect(Database.TraceEventCode.profile.rawValue == UInt32(SQLITE_TRACE_PROFILE)) - #expect(Database.TraceEventCode.row.rawValue == UInt32(SQLITE_TRACE_ROW)) - #expect(Database.TraceEventCode.close.rawValue == UInt32(SQLITE_TRACE_CLOSE)) + #expect(Connection.TraceEventCode.statement.rawValue == UInt32(SQLITE_TRACE_STMT)) + #expect(Connection.TraceEventCode.profile.rawValue == UInt32(SQLITE_TRACE_PROFILE)) + #expect(Connection.TraceEventCode.row.rawValue == UInt32(SQLITE_TRACE_ROW)) + #expect(Connection.TraceEventCode.close.rawValue == UInt32(SQLITE_TRACE_CLOSE)) } @Test("Trace event descriptions map values") @@ -50,17 +50,17 @@ struct DatabaseTraceTests { guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) else { return } - #expect(Database.TraceEventCallbackResult.ok.description == "ok") - #expect(Database.TraceEventCallbackResult(rawValue: 9).description == "unknown") - #expect(Database.TraceEventCallbackResult.ok.debugDescription == "ok (0)") - #expect(Database.TraceEventCode([]).description == "[]") - #expect(Database.TraceEventCode.statement.description.contains(".statement")) - #expect(Database.TraceEventCode(rawValue: 0x80).description == "unknown") - let mixed = Database.TraceEventCode(rawValue: Database.TraceEventCode.row.rawValue | 0x80) + #expect(Connection.TraceEventCallbackResult.ok.description == "ok") + #expect(Connection.TraceEventCallbackResult(rawValue: 9).description == "unknown") + #expect(Connection.TraceEventCallbackResult.ok.debugDescription == "ok (0)") + #expect(Connection.TraceEventCode([]).description == "[]") + #expect(Connection.TraceEventCode.statement.description.contains(".statement")) + #expect(Connection.TraceEventCode(rawValue: 0x80).description == "unknown") + let mixed = Connection.TraceEventCode(rawValue: Connection.TraceEventCode.row.rawValue | 0x80) #expect(mixed.description.contains("unknown")) - #expect(Database.TraceEventCode.statement.debugDescription.contains("SQLITE_TRACE_STMT")) + #expect(Connection.TraceEventCode.statement.debugDescription.contains("SQLITE_TRACE_STMT")) #expect(mixed.debugDescription.contains("0x")) - #expect(Database.TraceEventCode(rawValue: 0x80).debugDescription.hasPrefix("0x")) + #expect(Connection.TraceEventCode(rawValue: 0x80).debugDescription.hasPrefix("0x")) } @Test("setTraceCallback registers callback") @@ -68,22 +68,24 @@ struct DatabaseTraceTests { guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) else { return } - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var probe = TraceProbe() - #expect(openDatabase.setTraceCallback(for: [.statement], userData: &probe, callback: traceCallback) == .ok) + #expect(connection.setTraceCallback(for: [.statement], userData: &probe, callback: traceCallback) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .done) #expect(prepared.finalize() == .ok) #expect(probe.called) - #expect(openDatabase.setTraceCallback(for: [], userData: nil, callback: nil) == .ok) - _ = openDatabase.close() + #expect(connection.setTraceCallback(for: [], userData: nil, callback: nil) == .ok) + _ = connection.close() } } @@ -94,9 +96,9 @@ private struct TraceProbe { @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) private func traceCallback(_ traceEventCode: UInt32, _ userData: UnsafeMutableRawPointer?, _ p: UnsafeMutableRawPointer?, _ x: UnsafeMutableRawPointer?) -> Int32 { guard let userData else { - return Database.TraceEventCallbackResult.ok.rawValue + return Connection.TraceEventCallbackResult.ok.rawValue } let probe = userData.assumingMemoryBound(to: TraceProbe.self) probe.pointee.called = true - return Database.TraceEventCallbackResult.ok.rawValue + return Connection.TraceEventCallbackResult.ok.rawValue } diff --git a/Tests/LSQLiteTests/Database/DatabaseTests.swift b/Tests/LSQLiteTests/Connection/ConnectionTests.swift similarity index 52% rename from Tests/LSQLiteTests/Database/DatabaseTests.swift rename to Tests/LSQLiteTests/Connection/ConnectionTests.swift index 5bd2bd8..9327fdd 100644 --- a/Tests/LSQLiteTests/Database/DatabaseTests.swift +++ b/Tests/LSQLiteTests/Connection/ConnectionTests.swift @@ -1,12 +1,12 @@ import LSQLite import Testing -@Suite("Database") -struct DatabaseRawValueTests { +@Suite("Connection") +struct ConnectionRawValueTests { @Test("init(rawValue:) preserves rawValue") func rawValueRoundTrip() { let rawValue = OpaquePointer(bitPattern: 0x1)! - let database = Database(rawValue: rawValue) - #expect(database.rawValue == rawValue) + let connection = Connection(rawValue: rawValue) + #expect(connection.rawValue == rawValue) } } diff --git a/Tests/LSQLiteTests/Context/Context+AggregateTests.swift b/Tests/LSQLiteTests/Context/Context+AggregateTests.swift index e141e15..c29f6cc 100644 --- a/Tests/LSQLiteTests/Context/Context+AggregateTests.swift +++ b/Tests/LSQLiteTests/Context/Context+AggregateTests.swift @@ -5,17 +5,19 @@ import Testing struct ContextAggregateTests { @Test("aggregateContext allocates state for aggregates") func aggregateContextAllocatesState() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - try #require(openDatabase.exec("CREATE TABLE agg(value INTEGER)") == .ok) - try #require(openDatabase.exec("INSERT INTO agg(value) VALUES (1), (2), (3)") == .ok) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + try #require(connection.exec("CREATE TABLE agg(value INTEGER)") == .ok) + try #require(connection.exec("INSERT INTO agg(value) VALUES (1), (2), (3)") == .ok) var probe = AggregateProbe() - #expect(openDatabase.createFunction(name: "ctx_count", argumentCount: 1, textEncoding: .utf8, userData: &probe, stepHandler: aggregateStep, finalHandler: aggregateFinal) == .ok) + #expect(connection.createFunction(name: "ctx_count", argumentCount: 1, textEncoding: .utf8, userData: &probe, stepHandler: aggregateStep, finalHandler: aggregateFinal) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_count(value) FROM agg", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ctx_count(value) FROM agg", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.columnInt64(at: 0) == 3) @@ -23,7 +25,7 @@ struct ContextAggregateTests { #expect(prepared.finalize() == .ok) #expect(probe.allocated) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Context/Context+AuxiliaryTests.swift b/Tests/LSQLiteTests/Context/Context+AuxiliaryTests.swift index d076810..d8a91f4 100644 --- a/Tests/LSQLiteTests/Context/Context+AuxiliaryTests.swift +++ b/Tests/LSQLiteTests/Context/Context+AuxiliaryTests.swift @@ -5,15 +5,17 @@ import Testing struct ContextAuxiliaryTests { @Test("setAuxiliaryData and getAuxiliaryData round-trip pointers") func auxiliaryDataRoundTrip() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var probe = ContextAuxiliaryProbe() - #expect(openDatabase.createFunction(name: "ctx_aux", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: contextAuxiliaryHandler) == .ok) + #expect(connection.createFunction(name: "ctx_aux", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: contextAuxiliaryHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_aux(1), ctx_aux(1)", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ctx_aux(1), ctx_aux(1)", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .done) @@ -22,7 +24,7 @@ struct ContextAuxiliaryTests { #expect(probe.callCount == 2) #expect(probe.initialWasNil) #expect(probe.setCalled) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Context/Context+ConnectionTests.swift b/Tests/LSQLiteTests/Context/Context+ConnectionTests.swift new file mode 100644 index 0000000..d5669e8 --- /dev/null +++ b/Tests/LSQLiteTests/Context/Context+ConnectionTests.swift @@ -0,0 +1,43 @@ +import LSQLite +import Testing + +@Suite("Context+Connection") +struct ContextConnectionTests { + @Test("connection returns function connection") + func connectionReturnsFunctionConnection() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + + var probe = ContextConnectionProbe() + #expect(connection.createFunction(name: "ctx_db", argumentCount: 0, textEncoding: .utf8, userData: &probe, funcHandler: contextConnectionHandler) == .ok) + + var statement: Statement? + try #require(Statement.prepare(&statement, sql: "SELECT ctx_db()", for: connection) == .ok) + let prepared = try #require(statement) + #expect(prepared.step() == .row) + #expect(prepared.step() == .done) + #expect(prepared.finalize() == .ok) + + #expect(probe.connectionPointer == connection.rawValue) + _ = connection.close() + } +} + +private struct ContextConnectionProbe { + var connectionPointer: OpaquePointer? +} + +private func contextConnectionHandler(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { + guard let context else { + return + } + let contextWrapper = Context(rawValue: context) + if let userData = contextWrapper.userData { + let probe = userData.assumingMemoryBound(to: ContextConnectionProbe.self) + probe.pointee.connectionPointer = contextWrapper.connection?.rawValue + } + contextWrapper.resultInt(1) +} diff --git a/Tests/LSQLiteTests/Context/Context+DatabaseTests.swift b/Tests/LSQLiteTests/Context/Context+DatabaseTests.swift deleted file mode 100644 index fe9326d..0000000 --- a/Tests/LSQLiteTests/Context/Context+DatabaseTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -import LSQLite -import Testing - -@Suite("Context+Database") -struct ContextDatabaseTests { - @Test("database returns function connection") - func databaseReturnsFunctionConnection() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - - var probe = ContextDatabaseProbe() - #expect(openDatabase.createFunction(name: "ctx_db", argumentCount: 0, textEncoding: .utf8, userData: &probe, funcHandler: contextDatabaseHandler) == .ok) - - var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_db()", for: openDatabase) == .ok) - let prepared = try #require(statement) - #expect(prepared.step() == .row) - #expect(prepared.step() == .done) - #expect(prepared.finalize() == .ok) - - #expect(probe.databasePointer == openDatabase.rawValue) - _ = openDatabase.close() - } -} - -private struct ContextDatabaseProbe { - var databasePointer: OpaquePointer? -} - -private func contextDatabaseHandler(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { - guard let context else { - return - } - let contextWrapper = Context(rawValue: context) - if let userData = contextWrapper.userData { - let probe = userData.assumingMemoryBound(to: ContextDatabaseProbe.self) - probe.pointee.databasePointer = contextWrapper.database?.rawValue - } - contextWrapper.resultInt(1) -} diff --git a/Tests/LSQLiteTests/Context/Context+ResultTests.swift b/Tests/LSQLiteTests/Context/Context+ResultTests.swift index 74fab03..34ccf78 100644 --- a/Tests/LSQLiteTests/Context/Context+ResultTests.swift +++ b/Tests/LSQLiteTests/Context/Context+ResultTests.swift @@ -3,26 +3,29 @@ import Testing @Suite("Context+Result") final class ContextResultTests { - private let database: Database + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection } deinit { - _ = database.close() + _ = connection.close() } @Test("resultInt, resultInt64, and resultDouble set values") func resultScalarValues() throws { - #expect(database.createFunction(name: "ctx_int", argumentCount: 0, textEncoding: .utf8, funcHandler: resultIntHandler) == .ok) - #expect(database.createFunction(name: "ctx_int64", argumentCount: 0, textEncoding: .utf8, funcHandler: resultInt64Handler) == .ok) - #expect(database.createFunction(name: "ctx_double", argumentCount: 0, textEncoding: .utf8, funcHandler: resultDoubleHandler) == .ok) + #expect(connection.createFunction(name: "ctx_int", argumentCount: 0, textEncoding: .utf8, funcHandler: resultIntHandler) == .ok) + #expect(connection.createFunction(name: "ctx_int64", argumentCount: 0, textEncoding: .utf8, funcHandler: resultInt64Handler) == .ok) + #expect(connection.createFunction(name: "ctx_double", argumentCount: 0, textEncoding: .utf8, funcHandler: resultDoubleHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_int()", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ctx_int()", for: connection) == .ok) let intStatement = try #require(statement) #expect(intStatement.step() == .row) #expect(intStatement.columnInt(at: 0) == 7) @@ -30,7 +33,7 @@ final class ContextResultTests { #expect(intStatement.finalize() == .ok) var int64Statement: Statement? - try #require(Statement.prepare(&int64Statement, sql: "SELECT ctx_int64()", for: database) == .ok) + try #require(Statement.prepare(&int64Statement, sql: "SELECT ctx_int64()", for: connection) == .ok) let preparedInt64 = try #require(int64Statement) #expect(preparedInt64.step() == .row) #expect(preparedInt64.columnInt64(at: 0) == 9_000_000_000) @@ -38,7 +41,7 @@ final class ContextResultTests { #expect(preparedInt64.finalize() == .ok) var doubleStatement: Statement? - try #require(Statement.prepare(&doubleStatement, sql: "SELECT ctx_double()", for: database) == .ok) + try #require(Statement.prepare(&doubleStatement, sql: "SELECT ctx_double()", for: connection) == .ok) let preparedDouble = try #require(doubleStatement) #expect(preparedDouble.step() == .row) #expect(preparedDouble.columnDouble(at: 0) == 3.25) @@ -48,12 +51,12 @@ final class ContextResultTests { @Test("resultText, resultNull, and resultValue set values") func resultTextNullAndValue() throws { - #expect(database.createFunction(name: "ctx_text", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTextHandler) == .ok) - #expect(database.createFunction(name: "ctx_null", argumentCount: 0, textEncoding: .utf8, funcHandler: resultNullHandler) == .ok) - #expect(database.createFunction(name: "ctx_value", argumentCount: 1, textEncoding: .utf8, funcHandler: resultValueHandler) == .ok) + #expect(connection.createFunction(name: "ctx_text", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTextHandler) == .ok) + #expect(connection.createFunction(name: "ctx_null", argumentCount: 0, textEncoding: .utf8, funcHandler: resultNullHandler) == .ok) + #expect(connection.createFunction(name: "ctx_value", argumentCount: 1, textEncoding: .utf8, funcHandler: resultValueHandler) == .ok) var textStatement: Statement? - try #require(Statement.prepare(&textStatement, sql: "SELECT ctx_text()", for: database) == .ok) + try #require(Statement.prepare(&textStatement, sql: "SELECT ctx_text()", for: connection) == .ok) let preparedText = try #require(textStatement) #expect(preparedText.step() == .row) #expect(preparedText.columnText(at: 0) == "hello") @@ -61,7 +64,7 @@ final class ContextResultTests { #expect(preparedText.finalize() == .ok) var nullStatement: Statement? - try #require(Statement.prepare(&nullStatement, sql: "SELECT ctx_null()", for: database) == .ok) + try #require(Statement.prepare(&nullStatement, sql: "SELECT ctx_null()", for: connection) == .ok) let preparedNull = try #require(nullStatement) #expect(preparedNull.step() == .row) #expect(preparedNull.columnType(at: 0) == .null) @@ -69,7 +72,7 @@ final class ContextResultTests { #expect(preparedNull.finalize() == .ok) var valueStatement: Statement? - try #require(Statement.prepare(&valueStatement, sql: "SELECT ctx_value(42)", for: database) == .ok) + try #require(Statement.prepare(&valueStatement, sql: "SELECT ctx_value(42)", for: connection) == .ok) let preparedValue = try #require(valueStatement) #expect(preparedValue.step() == .row) #expect(preparedValue.columnInt(at: 0) == 42) @@ -79,13 +82,13 @@ final class ContextResultTests { @Test("resultBlob variants set blob values") func resultBlobVariants() throws { - #expect(database.createFunction(name: "ctx_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultBlobHandler) == .ok) - #expect(database.createFunction(name: "ctx_transient_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTransientBlobHandler) == .ok) - #expect(database.createFunction(name: "ctx_static_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultStaticBlobHandler) == .ok) - #expect(database.createFunction(name: "ctx_zero_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultZeroBlobHandler) == .ok) + #expect(connection.createFunction(name: "ctx_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultBlobHandler) == .ok) + #expect(connection.createFunction(name: "ctx_transient_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTransientBlobHandler) == .ok) + #expect(connection.createFunction(name: "ctx_static_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultStaticBlobHandler) == .ok) + #expect(connection.createFunction(name: "ctx_zero_blob", argumentCount: 0, textEncoding: .utf8, funcHandler: resultZeroBlobHandler) == .ok) var blobStatement: Statement? - try #require(Statement.prepare(&blobStatement, sql: "SELECT ctx_blob()", for: database) == .ok) + try #require(Statement.prepare(&blobStatement, sql: "SELECT ctx_blob()", for: connection) == .ok) let preparedBlob = try #require(blobStatement) #expect(preparedBlob.step() == .row) #expect(preparedBlob.columnBytes(at: 0) == 2) @@ -93,7 +96,7 @@ final class ContextResultTests { #expect(preparedBlob.finalize() == .ok) var transientStatement: Statement? - try #require(Statement.prepare(&transientStatement, sql: "SELECT ctx_transient_blob()", for: database) == .ok) + try #require(Statement.prepare(&transientStatement, sql: "SELECT ctx_transient_blob()", for: connection) == .ok) let preparedTransient = try #require(transientStatement) #expect(preparedTransient.step() == .row) #expect(preparedTransient.columnBytes(at: 0) == 3) @@ -101,7 +104,7 @@ final class ContextResultTests { #expect(preparedTransient.finalize() == .ok) var staticStatement: Statement? - try #require(Statement.prepare(&staticStatement, sql: "SELECT ctx_static_blob()", for: database) == .ok) + try #require(Statement.prepare(&staticStatement, sql: "SELECT ctx_static_blob()", for: connection) == .ok) let preparedStatic = try #require(staticStatement) #expect(preparedStatic.step() == .row) #expect(preparedStatic.columnBytes(at: 0) == 2) @@ -109,7 +112,7 @@ final class ContextResultTests { #expect(preparedStatic.finalize() == .ok) var zeroStatement: Statement? - try #require(Statement.prepare(&zeroStatement, sql: "SELECT ctx_zero_blob()", for: database) == .ok) + try #require(Statement.prepare(&zeroStatement, sql: "SELECT ctx_zero_blob()", for: connection) == .ok) let preparedZero = try #require(zeroStatement) #expect(preparedZero.step() == .row) #expect(preparedZero.columnBytes(at: 0) == 4) @@ -119,34 +122,34 @@ final class ContextResultTests { @Test("error result helpers produce expected codes") func errorResultHelpers() throws { - #expect(database.createFunction(name: "ctx_error", argumentCount: 0, textEncoding: .utf8, funcHandler: resultErrorHandler) == .ok) - #expect(database.createFunction(name: "ctx_error_code", argumentCount: 0, textEncoding: .utf8, funcHandler: resultErrorCodeHandler) == .ok) - #expect(database.createFunction(name: "ctx_toobig", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTooBigHandler) == .ok) - #expect(database.createFunction(name: "ctx_nomem", argumentCount: 0, textEncoding: .utf8, funcHandler: resultNoMemoryHandler) == .ok) + #expect(connection.createFunction(name: "ctx_error", argumentCount: 0, textEncoding: .utf8, funcHandler: resultErrorHandler) == .ok) + #expect(connection.createFunction(name: "ctx_error_code", argumentCount: 0, textEncoding: .utf8, funcHandler: resultErrorCodeHandler) == .ok) + #expect(connection.createFunction(name: "ctx_toobig", argumentCount: 0, textEncoding: .utf8, funcHandler: resultTooBigHandler) == .ok) + #expect(connection.createFunction(name: "ctx_nomem", argumentCount: 0, textEncoding: .utf8, funcHandler: resultNoMemoryHandler) == .ok) var errorStatement: Statement? - try #require(Statement.prepare(&errorStatement, sql: "SELECT ctx_error()", for: database) == .ok) + try #require(Statement.prepare(&errorStatement, sql: "SELECT ctx_error()", for: connection) == .ok) let preparedError = try #require(errorStatement) let errorResult = preparedError.step() #expect(errorResult != .row && errorResult != .done) _ = preparedError.finalize() var errorCodeStatement: Statement? - try #require(Statement.prepare(&errorCodeStatement, sql: "SELECT ctx_error_code()", for: database) == .ok) + try #require(Statement.prepare(&errorCodeStatement, sql: "SELECT ctx_error_code()", for: connection) == .ok) let preparedErrorCode = try #require(errorCodeStatement) let errorCodeResult = preparedErrorCode.step() #expect(errorCodeResult != .row && errorCodeResult != .done) _ = preparedErrorCode.finalize() var tooBigStatement: Statement? - try #require(Statement.prepare(&tooBigStatement, sql: "SELECT ctx_toobig()", for: database) == .ok) + try #require(Statement.prepare(&tooBigStatement, sql: "SELECT ctx_toobig()", for: connection) == .ok) let preparedTooBig = try #require(tooBigStatement) let tooBigResult = preparedTooBig.step() #expect(tooBigResult != .row && tooBigResult != .done) _ = preparedTooBig.finalize() var noMemoryStatement: Statement? - try #require(Statement.prepare(&noMemoryStatement, sql: "SELECT ctx_nomem()", for: database) == .ok) + try #require(Statement.prepare(&noMemoryStatement, sql: "SELECT ctx_nomem()", for: connection) == .ok) let preparedNoMem = try #require(noMemoryStatement) let noMemoryResult = preparedNoMem.step() #expect(noMemoryResult != .row && noMemoryResult != .done) @@ -156,9 +159,9 @@ final class ContextResultTests { @Test("resultSubtype assigns subtype when available") @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) func resultSubtypeAssignsSubtype() throws { - #expect(database.createFunction(name: "ctx_subtype", argumentCount: 0, textEncoding: .utf8, flags: [.resultSubtype], funcHandler: resultSubtypeHandler) == .ok) + #expect(connection.createFunction(name: "ctx_subtype", argumentCount: 0, textEncoding: .utf8, flags: [.resultSubtype], funcHandler: resultSubtypeHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_subtype()", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ctx_subtype()", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .done) diff --git a/Tests/LSQLiteTests/Context/Context+UserDataTests.swift b/Tests/LSQLiteTests/Context/Context+UserDataTests.swift index f8aabdd..316d55a 100644 --- a/Tests/LSQLiteTests/Context/Context+UserDataTests.swift +++ b/Tests/LSQLiteTests/Context/Context+UserDataTests.swift @@ -5,22 +5,24 @@ import Testing struct ContextUserDataTests { @Test("userData returns the registered pointer") func userDataReturnsRegisteredPointer() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var probe = ContextUserDataProbe() - #expect(openDatabase.createFunction(name: "ctx_user_data", argumentCount: 0, textEncoding: .utf8, userData: &probe, funcHandler: contextUserDataHandler) == .ok) + #expect(connection.createFunction(name: "ctx_user_data", argumentCount: 0, textEncoding: .utf8, userData: &probe, funcHandler: contextUserDataHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ctx_user_data()", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ctx_user_data()", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .done) #expect(prepared.finalize() == .ok) #expect(probe.matched) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Database/Database+AuthorizerTests.swift b/Tests/LSQLiteTests/Database/Database+AuthorizerTests.swift deleted file mode 100644 index 263c211..0000000 --- a/Tests/LSQLiteTests/Database/Database+AuthorizerTests.swift +++ /dev/null @@ -1,118 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Authorizer") -struct DatabaseAuthorizerTests { - @Test("AuthorizerHandlerResult init(rawValue:) preserves rawValue") - func authorizerHandlerResultRawValueRoundTrip() { - let rawValue = Int32(8) - let result = Database.AuthorizerHandlerResult(rawValue: rawValue) - #expect(result.rawValue == rawValue) - } - - @Test("AuthorizerHandlerActionCode init(rawValue:) preserves rawValue") - func authorizerHandlerActionCodeRawValueRoundTrip() { - let rawValue = Int32(9) - let actionCode = Database.AuthorizerHandlerActionCode(rawValue: rawValue) - #expect(actionCode.rawValue == rawValue) - } - - @Test("AuthorizerHandlerResult constants match SQLite") - func authorizerHandlerResultConstantsMatchSQLite() { - #expect(Database.AuthorizerHandlerResult.ok.rawValue == SQLITE_OK) - #expect(Database.AuthorizerHandlerResult.deny.rawValue == SQLITE_DENY) - #expect(Database.AuthorizerHandlerResult.ignore.rawValue == SQLITE_IGNORE) - } - - @Test("AuthorizerHandlerActionCode constants match SQLite") - func authorizerHandlerActionCodeConstantsMatchSQLite() { - #expect(Database.AuthorizerHandlerActionCode.createIndex.rawValue == SQLITE_CREATE_INDEX) - #expect(Database.AuthorizerHandlerActionCode.createTable.rawValue == SQLITE_CREATE_TABLE) - #expect(Database.AuthorizerHandlerActionCode.createTempIndex.rawValue == SQLITE_CREATE_TEMP_INDEX) - #expect(Database.AuthorizerHandlerActionCode.createTempTable.rawValue == SQLITE_CREATE_TEMP_TABLE) - #expect(Database.AuthorizerHandlerActionCode.createTempTrigger.rawValue == SQLITE_CREATE_TEMP_TRIGGER) - #expect(Database.AuthorizerHandlerActionCode.createTempView.rawValue == SQLITE_CREATE_TEMP_VIEW) - #expect(Database.AuthorizerHandlerActionCode.createTrigger.rawValue == SQLITE_CREATE_TRIGGER) - #expect(Database.AuthorizerHandlerActionCode.createView.rawValue == SQLITE_CREATE_VIEW) - #expect(Database.AuthorizerHandlerActionCode.delete.rawValue == SQLITE_DELETE) - #expect(Database.AuthorizerHandlerActionCode.dropIndex.rawValue == SQLITE_DROP_INDEX) - #expect(Database.AuthorizerHandlerActionCode.dropTable.rawValue == SQLITE_DROP_TABLE) - #expect(Database.AuthorizerHandlerActionCode.dropTempIndex.rawValue == SQLITE_DROP_TEMP_INDEX) - #expect(Database.AuthorizerHandlerActionCode.dropTempTable.rawValue == SQLITE_DROP_TEMP_TABLE) - #expect(Database.AuthorizerHandlerActionCode.dropTempTrigger.rawValue == SQLITE_DROP_TEMP_TRIGGER) - #expect(Database.AuthorizerHandlerActionCode.dropTempView.rawValue == SQLITE_DROP_TEMP_VIEW) - #expect(Database.AuthorizerHandlerActionCode.dropTrigger.rawValue == SQLITE_DROP_TRIGGER) - #expect(Database.AuthorizerHandlerActionCode.dropView.rawValue == SQLITE_DROP_VIEW) - #expect(Database.AuthorizerHandlerActionCode.insert.rawValue == SQLITE_INSERT) - #expect(Database.AuthorizerHandlerActionCode.pragma.rawValue == SQLITE_PRAGMA) - #expect(Database.AuthorizerHandlerActionCode.read.rawValue == SQLITE_READ) - #expect(Database.AuthorizerHandlerActionCode.select.rawValue == SQLITE_SELECT) - #expect(Database.AuthorizerHandlerActionCode.transaction.rawValue == SQLITE_TRANSACTION) - #expect(Database.AuthorizerHandlerActionCode.update.rawValue == SQLITE_UPDATE) - #expect(Database.AuthorizerHandlerActionCode.attach.rawValue == SQLITE_ATTACH) - #expect(Database.AuthorizerHandlerActionCode.detach.rawValue == SQLITE_DETACH) - #expect(Database.AuthorizerHandlerActionCode.alterTable.rawValue == SQLITE_ALTER_TABLE) - #expect(Database.AuthorizerHandlerActionCode.reindex.rawValue == SQLITE_REINDEX) - #expect(Database.AuthorizerHandlerActionCode.analyze.rawValue == SQLITE_ANALYZE) - #expect(Database.AuthorizerHandlerActionCode.createVTable.rawValue == SQLITE_CREATE_VTABLE) - #expect(Database.AuthorizerHandlerActionCode.dropVTable.rawValue == SQLITE_DROP_VTABLE) - #expect(Database.AuthorizerHandlerActionCode.function.rawValue == SQLITE_FUNCTION) - #expect(Database.AuthorizerHandlerActionCode.savepoint.rawValue == SQLITE_SAVEPOINT) - #expect(Database.AuthorizerHandlerActionCode.copy.rawValue == SQLITE_COPY) - #expect(Database.AuthorizerHandlerActionCode.recursive.rawValue == SQLITE_RECURSIVE) - } - - @Test("AuthorizerHandlerResult descriptions map values") - func authorizerHandlerResultDescriptions() { - #expect(Database.AuthorizerHandlerResult.ok.description == "ok") - #expect(Database.AuthorizerHandlerResult.deny.description == "deny") - #expect(Database.AuthorizerHandlerResult.ignore.description == "ignore") - #expect(Database.AuthorizerHandlerResult(rawValue: 77).description == "unknown") - #expect(Database.AuthorizerHandlerResult.ok.debugDescription == "SQLITE_OK") - #expect(Database.AuthorizerHandlerResult(rawValue: 77).debugDescription == "77") - } - - @Test("AuthorizerHandlerActionCode descriptions map values") - func authorizerHandlerActionCodeDescriptions() { - #expect(Database.AuthorizerHandlerActionCode.createIndex.description == "create index") - #expect(Database.AuthorizerHandlerActionCode.dropTable.description == "drop table") - #expect(Database.AuthorizerHandlerActionCode.select.description == "select") - #expect(Database.AuthorizerHandlerActionCode(rawValue: -1).description == "unknown") - #expect(Database.AuthorizerHandlerActionCode.createIndex.debugDescription == "SQLITE_CREATE_INDEX") - #expect(Database.AuthorizerHandlerActionCode(rawValue: -1).debugDescription == "-1") - } - - @Test("setAuthorizerHandler registers callback") - func setAuthorizerHandlerRegistersCallback() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - try #require(openDatabase.exec("CREATE TABLE auth(id INTEGER)") == .ok) - - var probe = AuthorizerProbe() - #expect(openDatabase.setAuthorizerHandler(userData: &probe, authorizerHandler) == .ok) - - var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT id FROM auth", for: openDatabase) == .ok) - let prepared = try #require(statement) - #expect(prepared.finalize() == .ok) - - #expect(probe.called) - #expect(openDatabase.setAuthorizerHandler(userData: nil, nil) == .ok) - _ = openDatabase.close() - } -} - -private struct AuthorizerProbe { - var called = false -} - -private func authorizerHandler(_ userData: UnsafeMutableRawPointer?, _ actionCode: Int32, _ detail1: UnsafePointer?, _ detail2: UnsafePointer?, _ databaseName: UnsafePointer?, _ triggerOrViewName: UnsafePointer?) -> Int32 { - guard let userData else { - return Database.AuthorizerHandlerResult.ok.rawValue - } - let probe = userData.assumingMemoryBound(to: AuthorizerProbe.self) - probe.pointee.called = true - return Database.AuthorizerHandlerResult.ok.rawValue -} diff --git a/Tests/LSQLiteTests/Database/Database+AutocommitTests.swift b/Tests/LSQLiteTests/Database/Database+AutocommitTests.swift deleted file mode 100644 index 225f808..0000000 --- a/Tests/LSQLiteTests/Database/Database+AutocommitTests.swift +++ /dev/null @@ -1,28 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Autocommit") -final class DatabaseAutocommitTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("isAutocommit reflects transaction state") - func autocommitReflectsTransactionState() throws { - #expect(database.isAutocommit) - - try #require(database.exec("BEGIN") == .ok) - #expect(!database.isAutocommit) - - try #require(database.exec("COMMIT") == .ok) - #expect(database.isAutocommit) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+BlobTests.swift b/Tests/LSQLiteTests/Database/Database+BlobTests.swift deleted file mode 100644 index 88bb811..0000000 --- a/Tests/LSQLiteTests/Database/Database+BlobTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Blob") -struct DatabaseOpenBlobFlagRawValueTests { - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(2) - let openBlobFlag = Database.OpenBlobFlag(rawValue: rawValue) - #expect(openBlobFlag.rawValue == rawValue) - } - - @Test("OpenBlobFlag descriptions map values") - func openBlobFlagDescriptions() { - #expect(Database.OpenBlobFlag.readonly.description == "readonly") - #expect(Database.OpenBlobFlag.readwrite.description == "readwrite") - #expect(Database.OpenBlobFlag(rawValue: 5).description == "5") - #expect(Database.OpenBlobFlag.readonly.debugDescription == "readonly (0)") - } - - @Test("openBlob returns a blob handle") - func openBlobReturnsBlobHandle() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - try #require(openDatabase.exec("CREATE TABLE blobs(data BLOB NOT NULL)") == .ok) - try #require(openDatabase.exec("INSERT INTO blobs(data) VALUES (zeroblob(2))") == .ok) - let rowID = openDatabase.lastInsertedRowID() - - var blob: Blob? - #expect(openDatabase.openBlob(&blob, databaseName: "main", tableName: "blobs", columnName: "data", rowID: rowID, flags: .readonly) == .ok) - let openedBlob = try #require(blob) - #expect(openedBlob.close() == .ok) - _ = openDatabase.close() - } -} diff --git a/Tests/LSQLiteTests/Database/Database+BusyTests.swift b/Tests/LSQLiteTests/Database/Database+BusyTests.swift deleted file mode 100644 index c8b0053..0000000 --- a/Tests/LSQLiteTests/Database/Database+BusyTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Busy") -final class DatabaseBusyHandlerResultRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(17) - let busyHandlerResult = Database.BusyHandlerResult(rawValue: rawValue) - #expect(busyHandlerResult.rawValue == rawValue) - } - - @Test("BusyHandlerResult descriptions map values") - func busyHandlerResultDescriptions() { - #expect(Database.BusyHandlerResult.break.description == "break") - #expect(Database.BusyHandlerResult.continue.description == "continue") - #expect(Database.BusyHandlerResult(rawValue: 99).description == "unknown") - #expect(Database.BusyHandlerResult.break.debugDescription == "break (0)") - #expect(Database.BusyHandlerResult(rawValue: 99).debugDescription == "unknown (99)") - } - - @Test("setBusyHandler registers and clears handler") - func setBusyHandlerRegistersAndClearsHandler() { - #expect(database.setBusyHandler(busyHandler) == .ok) - database.setTimerBusyHandler(milliseconds: 1) - #expect(database.setBusyHandler(userData: nil, nil) == .ok) - } -} - -private func busyHandler(_ userData: UnsafeMutableRawPointer?, _ attempt: Int32) -> Int32 { - Database.BusyHandlerResult.break.rawValue -} diff --git a/Tests/LSQLiteTests/Database/Database+ChangesTests.swift b/Tests/LSQLiteTests/Database/Database+ChangesTests.swift deleted file mode 100644 index fdb0194..0000000 --- a/Tests/LSQLiteTests/Database/Database+ChangesTests.swift +++ /dev/null @@ -1,30 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Changes") -final class DatabaseChangesTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec("CREATE TABLE changes(value TEXT)") == .ok) - } - - deinit { - _ = database.close() - } - - @Test("changes and totalChanges track writes") - func changesAndTotalChangesTrackWrites() throws { - try #require(database.exec("INSERT INTO changes(value) VALUES ('a')") == .ok) - #expect(database.changes == 1) - let totalAfterInsert = database.totalChanges - - try #require(database.exec("UPDATE changes SET value = 'b'") == .ok) - #expect(database.changes == 1) - #expect(database.totalChanges == totalAfterInsert + 1) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+CheckpointTests.swift b/Tests/LSQLiteTests/Database/Database+CheckpointTests.swift deleted file mode 100644 index 721ff6d..0000000 --- a/Tests/LSQLiteTests/Database/Database+CheckpointTests.swift +++ /dev/null @@ -1,55 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Checkpoint") -final class DatabaseCheckpointModeRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(9) - let mode = Database.CheckpointMode(rawValue: rawValue) - #expect(mode.rawValue == rawValue) - } - - @Test("CheckpointMode constants match SQLite") - func checkpointModeConstantsMatchSQLite() { - #expect(Database.CheckpointMode.passive.rawValue == SQLITE_CHECKPOINT_PASSIVE) - #expect(Database.CheckpointMode.full.rawValue == SQLITE_CHECKPOINT_FULL) - #expect(Database.CheckpointMode.restart.rawValue == SQLITE_CHECKPOINT_RESTART) - #expect(Database.CheckpointMode.truncate.rawValue == SQLITE_CHECKPOINT_TRUNCATE) - } - - @Test("CheckpointMode descriptions map values") - func checkpointModeDescriptions() { - #expect(Database.CheckpointMode.passive.description == "passive") - #expect(Database.CheckpointMode.full.description == "full") - #expect(Database.CheckpointMode.restart.description == "restart") - #expect(Database.CheckpointMode.truncate.description == "truncate") - #expect(Database.CheckpointMode(rawValue: -2).description == "unknown") - #expect(Database.CheckpointMode.passive.debugDescription == "SQLITE_CHECKPOINT_PASSIVE") - #expect(Database.CheckpointMode(rawValue: -2).debugDescription == "-2") - } - - @Test("autoWALCheckpoint and walCheckpoint return results") - func autoWALCheckpointAndWalCheckpointReturnResults() { - #expect(database.autoWALCheckpoint(pageInWALFileCount: 0) == .ok) - var frameCount: Int32 = -1 - var totalFrameCount: Int32 = -1 - let result = database.walCheckpoint("main", mode: .passive, frameCount: &frameCount, totalFrameCount: &totalFrameCount) - #expect(result == .ok) - #expect(frameCount >= -1) - #expect(totalFrameCount >= -1) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+CloseTests.swift b/Tests/LSQLiteTests/Database/Database+CloseTests.swift deleted file mode 100644 index 9ef7868..0000000 --- a/Tests/LSQLiteTests/Database/Database+CloseTests.swift +++ /dev/null @@ -1,22 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Close") -struct DatabaseCloseTests { - @Test("close returns ok for open database") - func closeReturnsOk() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - #expect(openDatabase.close() == .ok) - } - - @Test("closeV2 returns ok for open database") - @available(iOS 8.2, macOS 10.10, tvOS 8.2, watchOS 2.0, *) - func closeV2ReturnsOk() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - #expect(openDatabase.closeV2() == .ok) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+CollationTests.swift b/Tests/LSQLiteTests/Database/Database+CollationTests.swift deleted file mode 100644 index acbc3ea..0000000 --- a/Tests/LSQLiteTests/Database/Database+CollationTests.swift +++ /dev/null @@ -1,81 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Collation") -final class DatabaseCollationFlagRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(3) - let collationFlag = Database.CollationFlag(rawValue: rawValue) - #expect(collationFlag.rawValue == rawValue) - } - - @Test("CollationFlag constants match SQLite") - func collationFlagConstantsMatchSQLite() { - #expect(Database.CollationFlag.utf8.rawValue == SQLITE_UTF8) - #expect(Database.CollationFlag.utf16le.rawValue == SQLITE_UTF16LE) - #expect(Database.CollationFlag.utf16be.rawValue == SQLITE_UTF16BE) - #expect(Database.CollationFlag.utf16.rawValue == SQLITE_UTF16) - #expect(Database.CollationFlag.utf16Aligned.rawValue == SQLITE_UTF16_ALIGNED) - } - - @Test("CollationFlag descriptions map values") - func collationFlagDescriptions() { - #expect(Database.CollationFlag.utf8.description == "utf8") - #expect(Database.CollationFlag.utf16le.description == "utf16le") - #expect(Database.CollationFlag.utf16be.description == "utf16be") - #expect(Database.CollationFlag.utf16.description == "utf16") - #expect(Database.CollationFlag.utf16Aligned.description == "utf16Aligned") - #expect(Database.CollationFlag(rawValue: -2).description == "unknown") - #expect(Database.CollationFlag.utf8.debugDescription == "SQLITE_UTF8") - #expect(Database.CollationFlag(rawValue: -2).debugDescription == "-2") - } - - @Test("createCollation registers a comparator") - func createCollationRegistersComparator() { - #expect(database.createCollation(name: "always_equal", flag: .utf8, compareHandler: alwaysEqualCollation) == .ok) - } - - @Test("collationNeeded registers and invokes handler") - func collationNeededRegistersAndInvokesHandler() throws { - var probe = CollationNeededProbe() - #expect(database.collationNeeded(userData: &probe, neededHandler: collationNeededHandler) == .ok) - try #require(database.exec("SELECT 'a' = 'b' COLLATE needs_help") == .ok) - #expect(probe.called) - #expect(database.collationNeeded(userData: nil, neededHandler: nil) == .ok) - } -} - -private struct CollationNeededProbe { - var called = false -} - -private func alwaysEqualCollation(_ userData: UnsafeMutableRawPointer?, _ lhsLength: Int32, _ lhs: UnsafeRawPointer?, _ rhsLength: Int32, _ rhs: UnsafeRawPointer?) -> Int32 { - 0 -} - -private func collationNeededHandler(_ userData: UnsafeMutableRawPointer?, _ database: OpaquePointer?, _ collationFlag: Int32, _ name: UnsafePointer?) { - guard let userData else { - return - } - let probe = userData.assumingMemoryBound(to: CollationNeededProbe.self) - probe.pointee.called = true - guard let database, let name else { - return - } - let connection = Database(rawValue: database) - _ = connection.createCollation(name: String(cString: name), flag: Database.CollationFlag(rawValue: collationFlag), compareHandler: alwaysEqualCollation) -} diff --git a/Tests/LSQLiteTests/Database/Database+ErrorTests.swift b/Tests/LSQLiteTests/Database/Database+ErrorTests.swift deleted file mode 100644 index bf0754e..0000000 --- a/Tests/LSQLiteTests/Database/Database+ErrorTests.swift +++ /dev/null @@ -1,49 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Error") -final class DatabaseExtendedResultCodeStatusRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec("CREATE TABLE items(id INTEGER)") == .ok) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(42) - let status = Database.ExtendedResultCodeStatus(rawValue: rawValue) - #expect(status.rawValue == rawValue) - } - - @Test("ExtendedResultCodeStatus descriptions map values") - func extendedResultCodeStatusDescriptions() { - #expect(Database.ExtendedResultCodeStatus.off.description == "off") - #expect(Database.ExtendedResultCodeStatus.on.description == "on") - #expect(Database.ExtendedResultCodeStatus(rawValue: 3).description == "unknown") - #expect(Database.ExtendedResultCodeStatus.on.debugDescription == "on (1)") - #expect(Database.ExtendedResultCodeStatus(rawValue: 3).debugDescription == "unknown (3)") - } - - @Test("last error fields update after a failure") - func lastErrorFieldsUpdateAfterFailure() throws { - #expect(database.exec("INSERT INTO missing_table VALUES (1)") == .error) - #expect(database.lastErrorCode == .error) - #expect(database.lastExtendedErrorCode == .error) - #expect(!database.lastErrorMessage.isEmpty) - } - - @Test("setExtendedResultCodes toggles status") - func setExtendedResultCodesTogglesStatus() { - #expect(database.setExtendedResultCodes(.on) == .ok) - #expect(database.setExtendedResultCodes(.off) == .ok) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+ExecTests.swift b/Tests/LSQLiteTests/Database/Database+ExecTests.swift deleted file mode 100644 index e448f46..0000000 --- a/Tests/LSQLiteTests/Database/Database+ExecTests.swift +++ /dev/null @@ -1,66 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Exec") -final class DatabaseExecCallbackResultRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec("CREATE TABLE items(id INTEGER)") == .ok) - try #require(openDatabase.exec("INSERT INTO items(id) VALUES (1), (2)") == .ok) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(4) - let callbackResult = Database.ExecCallbackResult(rawValue: rawValue) - #expect(callbackResult.rawValue == rawValue) - } - - @Test("ExecCallbackResult descriptions map values") - func execCallbackResultDescriptions() { - #expect(Database.ExecCallbackResult.continue.description == "continue") - #expect(Database.ExecCallbackResult.abort.description == "abort") - #expect(Database.ExecCallbackResult(rawValue: 9).description == "unknown") - #expect(Database.ExecCallbackResult.continue.debugDescription == "continue (0)") - #expect(Database.ExecCallbackResult(rawValue: 9).debugDescription == "unknown (9)") - } - - @Test("exec invokes callback for rows") - func execInvokesCallbackForRows() throws { - var rowCount = Int32(0) - let result = database.exec("SELECT id FROM items ORDER BY id", userData: &rowCount, callback: execRowCounter) - #expect(result == .ok) - #expect(rowCount == 2) - } - - @Test("exec captures error messages") - func execCapturesErrorMessages() { - var errorMessage: UnsafeMutablePointer? = nil - defer { - if let errorMessage { - sqlite3_free(errorMessage) - } - } - #expect(database.exec("SELECT * FROM missing_table", errorMessage: &errorMessage) == .error) - #expect(errorMessage != nil) - } -} - -private func execRowCounter(_ userData: UnsafeMutableRawPointer?, _ count: Int32, _ values: UnsafeMutablePointer?>?, _ columns: UnsafeMutablePointer?>?) -> Int32 { - guard let userData else { - return Database.ExecCallbackResult.abort.rawValue - } - let rowCount = userData.assumingMemoryBound(to: Int32.self) - rowCount.pointee += 1 - return Database.ExecCallbackResult.continue.rawValue -} diff --git a/Tests/LSQLiteTests/Database/Database+FilenameTests.swift b/Tests/LSQLiteTests/Database/Database+FilenameTests.swift deleted file mode 100644 index 3e06a13..0000000 --- a/Tests/LSQLiteTests/Database/Database+FilenameTests.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation -import LSQLite -import Testing - -@Suite("Database+Filename") -struct DatabaseFilenameTests { - @Test("filename returns empty string for in-memory database") - func filenameReturnsEmptyStringForInMemoryDatabase() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - #expect(openDatabase.filename(forDatabaseNamed: "main") == "") - #expect(openDatabase.filename(forDatabaseNamed: "missing") == nil) - _ = openDatabase.close() - } - - @Test("filename returns a path for file database") - func filenameReturnsPathForFileDatabase() throws { - let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("lsqlite-filename.sqlite") - defer { - try? FileManager.default.removeItem(at: fileURL) - } - var database: Database? - try #require(Database.open(&database, at: .init(rawValue: fileURL.path), withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - let filename = openDatabase.filename(forDatabaseNamed: "main") - #expect(filename != nil) - #expect(filename?.isEmpty == false) - _ = openDatabase.close() - } -} diff --git a/Tests/LSQLiteTests/Database/Database+FunctionTests.swift b/Tests/LSQLiteTests/Database/Database+FunctionTests.swift deleted file mode 100644 index d781c69..0000000 --- a/Tests/LSQLiteTests/Database/Database+FunctionTests.swift +++ /dev/null @@ -1,121 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Function") -final class DatabaseFunctionTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("TextEncoding init(rawValue:) preserves rawValue") - func textEncodingRawValueRoundTrip() { - let rawValue = Int32(10) - let encoding = Database.TextEncoding(rawValue: rawValue) - #expect(encoding.rawValue == rawValue) - } - - @Test("FunctionFlag init(rawValue:) preserves rawValue") - func functionFlagRawValueRoundTrip() { - let rawValue = Int32(11) - let flag = Database.FunctionFlag(rawValue: rawValue) - #expect(flag.rawValue == rawValue) - } - - @Test("FunctionFlag init(rawValue:) preserves combined rawValue") - func functionFlagCombinedRawValueRoundTrip() { - let rawValue = Database.FunctionFlag.deterministic.rawValue | Database.FunctionFlag.directOnly.rawValue - let flag = Database.FunctionFlag(rawValue: rawValue) - #expect(flag.rawValue == rawValue) - } - - @Test("TextEncoding constants match SQLite") - func textEncodingConstantsMatchSQLite() { - #expect(Database.TextEncoding.utf8.rawValue == SQLITE_UTF8) - #expect(Database.TextEncoding.utf16le.rawValue == SQLITE_UTF16LE) - #expect(Database.TextEncoding.utf16be.rawValue == SQLITE_UTF16BE) - #expect(Database.TextEncoding.utf16.rawValue == SQLITE_UTF16) - #expect(Database.TextEncoding.any.rawValue == SQLITE_ANY) - } - - @Test("FunctionFlag constants match SQLite") - func functionFlagConstantsMatchSQLite() { - #expect(Database.FunctionFlag.deterministic.rawValue == SQLITE_DETERMINISTIC) - #expect(Database.FunctionFlag.directOnly.rawValue == SQLITE_DIRECTONLY) - #expect(Database.FunctionFlag.subtype.rawValue == SQLITE_SUBTYPE) - #expect(Database.FunctionFlag.innocuous.rawValue == SQLITE_INNOCUOUS) - #expect(Database.FunctionFlag.resultSubtype.rawValue == SQLITE_RESULT_SUBTYPE) - } - - @Test("TextEncoding descriptions map values") - func textEncodingDescriptions() { - #expect(Database.TextEncoding.utf8.description == "utf8") - #expect(Database.TextEncoding.utf16le.description == "utf16le") - #expect(Database.TextEncoding.utf16be.description == "utf16be") - #expect(Database.TextEncoding.utf16.description == "utf16") - #expect(Database.TextEncoding.any.description == "any") - #expect(Database.TextEncoding(rawValue: -1).description == "unknown") - #expect(Database.TextEncoding.utf8.debugDescription == "SQLITE_UTF8") - } - - @Test("FunctionFlag descriptions map values") - func functionFlagDescriptions() { - #expect(Database.FunctionFlag([]).description == "[]") - #expect(Database.FunctionFlag.deterministic.description.contains(".deterministic")) - let knownMask = UInt32(bitPattern: Database.FunctionFlag.deterministic.rawValue) - | UInt32(bitPattern: Database.FunctionFlag.directOnly.rawValue) - | UInt32(bitPattern: Database.FunctionFlag.subtype.rawValue) - | UInt32(bitPattern: Database.FunctionFlag.innocuous.rawValue) - | UInt32(bitPattern: Database.FunctionFlag.resultSubtype.rawValue) - let unknownOnlyRaw = Int32(bitPattern: ~knownMask) - #expect(Database.FunctionFlag(rawValue: unknownOnlyRaw).description == "unknown") - let mixed = Database.FunctionFlag(rawValue: Database.FunctionFlag.deterministic.rawValue | unknownOnlyRaw) - #expect(mixed.description.contains("unknown")) - #expect(Database.FunctionFlag.deterministic.debugDescription.contains("SQLITE_DETERMINISTIC")) - #expect(mixed.debugDescription.contains("0x")) - } - - @Test("createFunction registers scalar function") - func createFunctionRegistersScalarFunction() throws { - #expect(database.createFunction(name: "constant_seven", argumentCount: 0, textEncoding: .utf8, flags: [.deterministic], funcHandler: constantFunction) == .ok) - var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT constant_seven()", for: database) == .ok) - let prepared = try #require(statement) - #expect(prepared.step() == .row) - #expect(prepared.columnInt(at: 0) == 7) - #expect(prepared.step() == .done) - #expect(prepared.finalize() == .ok) - } - - @Test("createWindowFunction registers window function") - @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) - func createWindowFunctionRegistersWindowFunction() { - #expect(database.createWindowFunction(name: "window_zero", argumentCount: 0, textEncoding: .utf8, flags: [], stepHandler: windowStep, finalHandler: windowFinal, valueHandler: nil, inverseHandler: nil) == .ok) - } -} - -private func constantFunction(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { - guard let context else { - return - } - let contextWrapper = Context(rawValue: context) - contextWrapper.resultInt(7) -} - -private func windowStep(_ context: OpaquePointer?, _ valueCount: Int32, _ values: UnsafeMutablePointer?) { -} - -private func windowFinal(_ context: OpaquePointer?) { - guard let context else { - return - } - Context(rawValue: context).resultInt(0) -} diff --git a/Tests/LSQLiteTests/Database/Database+InterruptTests.swift b/Tests/LSQLiteTests/Database/Database+InterruptTests.swift deleted file mode 100644 index 35cf87e..0000000 --- a/Tests/LSQLiteTests/Database/Database+InterruptTests.swift +++ /dev/null @@ -1,14 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Interrupt") -struct DatabaseInterruptTests { - @Test("interrupt can be called on open database") - func interruptCanBeCalledOnOpenDatabase() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - openDatabase.interrupt() - _ = openDatabase.close() - } -} diff --git a/Tests/LSQLiteTests/Database/Database+LastInsertRowidTests.swift b/Tests/LSQLiteTests/Database/Database+LastInsertRowidTests.swift deleted file mode 100644 index a862a1a..0000000 --- a/Tests/LSQLiteTests/Database/Database+LastInsertRowidTests.swift +++ /dev/null @@ -1,30 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+LastInsertRowid") -final class DatabaseLastInsertRowidTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec("CREATE TABLE items(id INTEGER PRIMARY KEY)") == .ok) - } - - deinit { - _ = database.close() - } - - @Test("lastInsertedRowID and setLastInsertedRowID") - func lastInsertedRowIDAndSetLastInsertedRowID() throws { - try #require(database.exec("INSERT INTO items DEFAULT VALUES") == .ok) - let inserted = database.lastInsertedRowID() - #expect(inserted.rawValue > 0) - - let override = RowID(rawValue: 99) - database.setLastInsertedRowID(override) - #expect(database.lastInsertedRowID().rawValue == 99) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+LimitTests.swift b/Tests/LSQLiteTests/Database/Database+LimitTests.swift deleted file mode 100644 index 9d11edc..0000000 --- a/Tests/LSQLiteTests/Database/Database+LimitTests.swift +++ /dev/null @@ -1,68 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Limit") -final class DatabaseLimitCategoryRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(5) - let category = Database.LimitCategory(rawValue: rawValue) - #expect(category.rawValue == rawValue) - } - - @Test("LimitCategory constants match SQLite") - func limitCategoryConstantsMatchSQLite() { - #expect(Database.LimitCategory.length.rawValue == SQLITE_LIMIT_LENGTH) - #expect(Database.LimitCategory.sqlLength.rawValue == SQLITE_LIMIT_SQL_LENGTH) - #expect(Database.LimitCategory.column.rawValue == SQLITE_LIMIT_COLUMN) - #expect(Database.LimitCategory.exprDepth.rawValue == SQLITE_LIMIT_EXPR_DEPTH) - #expect(Database.LimitCategory.compoundSelect.rawValue == SQLITE_LIMIT_COMPOUND_SELECT) - #expect(Database.LimitCategory.vdbeOp.rawValue == SQLITE_LIMIT_VDBE_OP) - #expect(Database.LimitCategory.functionArg.rawValue == SQLITE_LIMIT_FUNCTION_ARG) - #expect(Database.LimitCategory.attached.rawValue == SQLITE_LIMIT_ATTACHED) - #expect(Database.LimitCategory.likePatternLength.rawValue == SQLITE_LIMIT_LIKE_PATTERN_LENGTH) - #expect(Database.LimitCategory.variableNumber.rawValue == SQLITE_LIMIT_VARIABLE_NUMBER) - #expect(Database.LimitCategory.triggerDepth.rawValue == SQLITE_LIMIT_TRIGGER_DEPTH) - #expect(Database.LimitCategory.workerThreads.rawValue == SQLITE_LIMIT_WORKER_THREADS) - } - - @Test("LimitCategory descriptions map values") - func limitCategoryDescriptions() { - #expect(Database.LimitCategory.length.description == "length") - #expect(Database.LimitCategory.sqlLength.description == "sql length") - #expect(Database.LimitCategory.column.description == "column") - #expect(Database.LimitCategory.exprDepth.description == "expression depth") - #expect(Database.LimitCategory.compoundSelect.description == "compound select") - #expect(Database.LimitCategory.vdbeOp.description == "vdbe op") - #expect(Database.LimitCategory.functionArg.description == "function arg") - #expect(Database.LimitCategory.attached.description == "attached") - #expect(Database.LimitCategory.likePatternLength.description == "like pattern length") - #expect(Database.LimitCategory.variableNumber.description == "variable number") - #expect(Database.LimitCategory.triggerDepth.description == "trigger depth") - #expect(Database.LimitCategory.workerThreads.description == "worker threads") - #expect(Database.LimitCategory(rawValue: -3).description == "unknown") - #expect(Database.LimitCategory.length.debugDescription == "SQLITE_LIMIT_LENGTH") - #expect(Database.LimitCategory(rawValue: -3).debugDescription == "-3") - } - - @Test("limit and setLimit round-trip values") - func limitAndSetLimitRoundTripValues() { - let current = database.limit(for: .length) - let previous = database.setLimit(current, for: .length) - #expect(previous == current) - #expect(database.limit(for: .length) == current) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+OpenTests.swift b/Tests/LSQLiteTests/Database/Database+OpenTests.swift deleted file mode 100644 index 7e09a3a..0000000 --- a/Tests/LSQLiteTests/Database/Database+OpenTests.swift +++ /dev/null @@ -1,85 +0,0 @@ -import LSQLite -import MissedSwiftSQLite -import Testing - -@Suite("Database+Open") -struct DatabaseOpenTests { - @Test("FileName init(rawValue:) preserves rawValue") - func fileNameRawValueRoundTrip() { - let rawValue = "test.db" - let fileName = Database.FileName(rawValue: rawValue) - #expect(fileName.rawValue == rawValue) - } - - @Test("OpenFlag init(rawValue:) preserves rawValue") - func openFlagRawValueRoundTrip() { - let rawValue = Int32(0x1200) - let openFlag = Database.OpenFlag(rawValue: rawValue) - #expect(openFlag.rawValue == rawValue) - } - - @Test("OpenFlag init(rawValue:) preserves combined rawValue") - func openFlagCombinedRawValueRoundTrip() { - let rawValue = Database.OpenFlag.readwrite.rawValue | Database.OpenFlag.create.rawValue - let openFlag = Database.OpenFlag(rawValue: rawValue) - #expect(openFlag.rawValue == rawValue) - } - - @Test("OpenFlag constants match SQLite") - func openFlagConstantsMatchSQLite() { - #expect(Database.OpenFlag.readonly.rawValue == SQLITE_OPEN_READONLY) - #expect(Database.OpenFlag.readwrite.rawValue == SQLITE_OPEN_READWRITE) - #expect(Database.OpenFlag.create.rawValue == SQLITE_OPEN_CREATE) - #expect(Database.OpenFlag.deleteOnClose.rawValue == SQLITE_OPEN_DELETEONCLOSE) - #expect(Database.OpenFlag.exclusive.rawValue == SQLITE_OPEN_EXCLUSIVE) - #expect(Database.OpenFlag.autoproxy.rawValue == SQLITE_OPEN_AUTOPROXY) - #expect(Database.OpenFlag.uri.rawValue == SQLITE_OPEN_URI) - #expect(Database.OpenFlag.memory.rawValue == SQLITE_OPEN_MEMORY) - #expect(Database.OpenFlag.mainDB.rawValue == SQLITE_OPEN_MAIN_DB) - #expect(Database.OpenFlag.tempDB.rawValue == SQLITE_OPEN_TEMP_DB) - #expect(Database.OpenFlag.transientDB.rawValue == SQLITE_OPEN_TRANSIENT_DB) - #expect(Database.OpenFlag.mainJournal.rawValue == SQLITE_OPEN_MAIN_JOURNAL) - #expect(Database.OpenFlag.tempJournal.rawValue == SQLITE_OPEN_TEMP_JOURNAL) - #expect(Database.OpenFlag.subjournal.rawValue == SQLITE_OPEN_SUBJOURNAL) - #expect(Database.OpenFlag.masterJournal.rawValue == SQLITE_OPEN_MASTER_JOURNAL) - #expect(Database.OpenFlag.noMutex.rawValue == SQLITE_OPEN_NOMUTEX) - #expect(Database.OpenFlag.fullMutex.rawValue == SQLITE_OPEN_FULLMUTEX) - #expect(Database.OpenFlag.sharedCache.rawValue == SQLITE_OPEN_SHAREDCACHE) - #expect(Database.OpenFlag.privateCache.rawValue == SQLITE_OPEN_PRIVATECACHE) - #expect(Database.OpenFlag.wal.rawValue == SQLITE_OPEN_WAL) -#if canImport(Darwin) - #expect(Database.OpenFlag.fileProtectionComplete.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETE) - #expect(Database.OpenFlag.fileProtectionCompleteUnlessOpen.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN) - #expect(Database.OpenFlag.fileProtectionCompleteUntilFirstUserAuthentication.rawValue == SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION) - #expect(Database.OpenFlag.fileProtectionNone.rawValue == SQLITE_OPEN_FILEPROTECTION_NONE) - #expect(Database.OpenFlag.fileProtectionMask.rawValue == SQLITE_OPEN_FILEPROTECTION_MASK) -#endif - } - - @Test("FileName description reflects rawValue") - func fileNameDescriptionReflectsRawValue() { - #expect(Database.FileName.memory.description == ":memory:") - #expect(Database.FileName.temporary.description == "") - #expect(Database.FileName(rawValue: "custom.db").description == "custom.db") - } - - @Test("OpenFlag descriptions map values") - func openFlagDescriptions() { - #expect(Database.OpenFlag([]).description == "[]") - #expect(Database.OpenFlag.readwrite.description.contains(".readwrite")) - #expect(Database.OpenFlag(rawValue: 0x4000_0000).description == "unknown") - let mixed = Database.OpenFlag(rawValue: Database.OpenFlag.readonly.rawValue | 0x4000_0000) - #expect(mixed.description.contains("unknown")) - #expect(Database.OpenFlag.readonly.debugDescription.contains("SQLITE_OPEN_READONLY")) - #expect(mixed.debugDescription.contains("0x")) - #expect(Database.OpenFlag(rawValue: 0x4000_0000).debugDescription.hasPrefix("0x")) - } - - @Test("open creates an in-memory database") - func openCreatesInMemoryDatabase() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - #expect(openDatabase.close() == .ok) - } -} diff --git a/Tests/LSQLiteTests/Database/Database+ProgressHandlerTests.swift b/Tests/LSQLiteTests/Database/Database+ProgressHandlerTests.swift deleted file mode 100644 index 2d5c711..0000000 --- a/Tests/LSQLiteTests/Database/Database+ProgressHandlerTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+ProgressHandler") -final class DatabaseProgressHandlerResultRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(8) - let result = Database.ProgressHandlerResult(rawValue: rawValue) - #expect(result.rawValue == rawValue) - } - - @Test("ProgressHandlerResult descriptions map values") - func progressHandlerResultDescriptions() { - #expect(Database.ProgressHandlerResult.continue.description == "continue") - #expect(Database.ProgressHandlerResult.interrupt.description == "interrupt") - #expect(Database.ProgressHandlerResult(rawValue: 2).description == "unknown") - #expect(Database.ProgressHandlerResult.continue.debugDescription == "continue (0)") - #expect(Database.ProgressHandlerResult(rawValue: 2).debugDescription == "unknown (2)") - } - - @Test("setProgressHandler registers handler") - func setProgressHandlerRegistersHandler() { - database.setProgressHandler(instructionCount: 10, handler: progressHandler) - database.setProgressHandler(instructionCount: 0, handler: nil) - } -} - -private func progressHandler(_ userData: UnsafeMutableRawPointer?) -> Int32 { - Database.ProgressHandlerResult.continue.rawValue -} diff --git a/Tests/LSQLiteTests/Database/Database+ReadonlyTests.swift b/Tests/LSQLiteTests/Database/Database+ReadonlyTests.swift deleted file mode 100644 index cdf47fe..0000000 --- a/Tests/LSQLiteTests/Database/Database+ReadonlyTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -import LSQLite -import Testing - -@Suite("Database+Readonly") -final class DatabaseReadWriteAccessStateRawValueTests { - private let database: Database - - init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) - } - - deinit { - _ = database.close() - } - - @Test("init(rawValue:) preserves rawValue") - func rawValueRoundTrip() { - let rawValue = Int32(-7) - let state = Database.ReadWriteAccessState(rawValue: rawValue) - #expect(state.rawValue == rawValue) - } - - @Test("ReadWriteAccessState descriptions map values") - func readWriteAccessStateDescriptions() { - #expect(Database.ReadWriteAccessState.noDatabase.description == "no database") - #expect(Database.ReadWriteAccessState.readwrite.description == "readwrite") - #expect(Database.ReadWriteAccessState.readonly.description == "readonly") - #expect(Database.ReadWriteAccessState(rawValue: 9).description == "unknown") - #expect(Database.ReadWriteAccessState.readwrite.debugDescription == "readwrite (0)") - #expect(Database.ReadWriteAccessState(rawValue: 9).debugDescription == "unknown (9)") - } - - @Test("readWriteAccessState returns status for named database") - func readWriteAccessStateReturnsStatusForNamedDatabase() { - #expect(database.readWriteAccessState(forDatabaseNamed: "main") == .readwrite) - #expect(database.readWriteAccessState(forDatabaseNamed: "missing") == .noDatabase) - } -} diff --git a/Tests/LSQLiteTests/Statement/Statement+BindTests.swift b/Tests/LSQLiteTests/Statement/Statement+BindTests.swift index 068021c..ed6fa5c 100644 --- a/Tests/LSQLiteTests/Statement/Statement+BindTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+BindTests.swift @@ -3,14 +3,16 @@ import Testing @Suite("Statement+Bind") final class StatementBindTests { - private let database: Database + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec(""" + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec(""" CREATE TABLE bindings( int_value INTEGER, double_value REAL, @@ -24,13 +26,13 @@ final class StatementBindTests { } deinit { - _ = database.close() + _ = connection.close() } @Test("binding metadata is available") func bindingMetadataIsAvailable() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ?1, :name, ?3", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ?1, :name, ?3", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindingCount == 3) let firstName = prepared.bindingName(at: 1) @@ -46,7 +48,7 @@ final class StatementBindTests { @Test("binds scalar and blob values") func bindsScalarAndBlobValues() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "INSERT INTO bindings(int_value, double_value, text_value, blob_value, zero_blob, null_value, int64_value) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "INSERT INTO bindings(int_value, double_value, text_value, blob_value, zero_blob, null_value, int64_value) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindInt(12, at: 1) == .ok) @@ -86,7 +88,7 @@ final class StatementBindTests { @Test("bindBlob uses destructor") func bindBlobUsesDestructor() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "INSERT INTO bindings(blob_value) VALUES (?1)", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "INSERT INTO bindings(blob_value) VALUES (?1)", for: connection) == .ok) let prepared = try #require(statement) let pointer = UnsafeMutableRawPointer.allocate(byteCount: 2, alignment: MemoryLayout.alignment) diff --git a/Tests/LSQLiteTests/Statement/Statement+BusyTests.swift b/Tests/LSQLiteTests/Statement/Statement+BusyTests.swift index 3b1f12a..a9c9d76 100644 --- a/Tests/LSQLiteTests/Statement/Statement+BusyTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+BusyTests.swift @@ -3,22 +3,25 @@ import Testing @Suite("Statement+Busy") final class StatementBusyTests { - private let database: Database + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection } deinit { - _ = database.close() + _ = connection.close() } @Test("isBusy reflects step progress") func isBusyReflectsStepProgress() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1 UNION ALL SELECT 2", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1 UNION ALL SELECT 2", for: connection) == .ok) let prepared = try #require(statement) #expect(!prepared.isBusy) diff --git a/Tests/LSQLiteTests/Statement/Statement+ColumnTests.swift b/Tests/LSQLiteTests/Statement/Statement+ColumnTests.swift index f11f590..b32b1f6 100644 --- a/Tests/LSQLiteTests/Statement/Statement+ColumnTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+ColumnTests.swift @@ -3,14 +3,16 @@ import Testing @Suite("Statement+Column") final class StatementColumnTests { - private let database: Database + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - self.database = openDatabase - try #require(openDatabase.exec(""" + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection + try #require(connection.exec(""" CREATE TABLE col_table( text_col TEXT, blob_col BLOB, @@ -19,17 +21,17 @@ final class StatementColumnTests { declared_col TEXT ) """) == .ok) - try #require(openDatabase.exec("INSERT INTO col_table(text_col, blob_col, int_col, real_col, declared_col) VALUES ('', X'0102', 42, 3.5, 'x')") == .ok) + try #require(connection.exec("INSERT INTO col_table(text_col, blob_col, int_col, real_col, declared_col) VALUES ('', X'0102', 42, 3.5, 'x')") == .ok) } deinit { - _ = database.close() + _ = connection.close() } @Test("column metadata and values are available") func columnMetadataAndValuesAreAvailable() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT text_col, blob_col, int_col, real_col, declared_col, NULL AS null_col, 99 AS literal_col FROM col_table", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT text_col, blob_col, int_col, real_col, declared_col, NULL AS null_col, 99 AS literal_col FROM col_table", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.columnCount == 7) @@ -68,12 +70,12 @@ final class StatementColumnTests { @Test("column metadata returns empty identifiers") func columnMetadataReturnsEmptyIdentifiers() throws { - try #require(database.exec("ATTACH DATABASE ':memory:' AS \"\"") == .ok) - try #require(database.exec("CREATE TABLE \"\".\"\" (\"\" \"\")") == .ok) - try #require(database.exec("INSERT INTO \"\".\"\" VALUES ('x')") == .ok) + try #require(connection.exec("ATTACH DATABASE ':memory:' AS \"\"") == .ok) + try #require(connection.exec("CREATE TABLE \"\".\"\" (\"\" \"\")") == .ok) + try #require(connection.exec("INSERT INTO \"\".\"\" VALUES ('x')") == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT \"\" FROM \"\".\"\"", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT \"\" FROM \"\".\"\"", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) diff --git a/Tests/LSQLiteTests/Statement/Statement+ConnectionTests.swift b/Tests/LSQLiteTests/Statement/Statement+ConnectionTests.swift new file mode 100644 index 0000000..4b2ba70 --- /dev/null +++ b/Tests/LSQLiteTests/Statement/Statement+ConnectionTests.swift @@ -0,0 +1,21 @@ +import LSQLite +import Testing + +@Suite("Statement+Connection") +struct StatementConnectionTests { + @Test("connection returns owning connection") + func connectionReturnsOwningConnection() throws { + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + + var statement: Statement? + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection) == .ok) + let prepared = try #require(statement) + #expect(prepared.connection?.rawValue == connection.rawValue) + #expect(prepared.finalize() == .ok) + _ = connection.close() + } +} diff --git a/Tests/LSQLiteTests/Statement/Statement+DatabaseTests.swift b/Tests/LSQLiteTests/Statement/Statement+DatabaseTests.swift deleted file mode 100644 index a8bff53..0000000 --- a/Tests/LSQLiteTests/Statement/Statement+DatabaseTests.swift +++ /dev/null @@ -1,19 +0,0 @@ -import LSQLite -import Testing - -@Suite("Statement+Database") -struct StatementDatabaseTests { - @Test("database returns owning connection") - func databaseReturnsOwningConnection() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - - var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: openDatabase) == .ok) - let prepared = try #require(statement) - #expect(prepared.database?.rawValue == openDatabase.rawValue) - #expect(prepared.finalize() == .ok) - _ = openDatabase.close() - } -} diff --git a/Tests/LSQLiteTests/Statement/Statement+FinalizeTests.swift b/Tests/LSQLiteTests/Statement/Statement+FinalizeTests.swift index ef62408..227e2c6 100644 --- a/Tests/LSQLiteTests/Statement/Statement+FinalizeTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+FinalizeTests.swift @@ -5,14 +5,16 @@ import Testing struct StatementFinalizeTests { @Test("finalize releases statement") func finalizeReleasesStatement() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Statement/Statement+PrepareTests.swift b/Tests/LSQLiteTests/Statement/Statement+PrepareTests.swift index 8132e83..aad646d 100644 --- a/Tests/LSQLiteTests/Statement/Statement+PrepareTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+PrepareTests.swift @@ -4,16 +4,19 @@ import Testing @Suite("Statement+Prepare") final class StatementPrepareFlagRawValueTests { - private let database: Database + private let connection: Connection init() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - self.database = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + self.connection = connection } deinit { - _ = database.close() + _ = connection.close() } @Test("init(rawValue:) preserves rawValue") func rawValueRoundTrip() { @@ -52,7 +55,7 @@ final class StatementPrepareFlagRawValueTests { func prepareCompilesSQLAndProvidesTail() throws { var statement: Statement? var tail: String? - try #require(Statement.prepare(&statement, sql: "SELECT 1; SELECT 2", tail: &tail, for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1; SELECT 2", tail: &tail, for: connection) == .ok) let prepared = try #require(statement) #expect(tail?.contains("SELECT 2") == true) #expect(prepared.finalize() == .ok) @@ -61,7 +64,7 @@ final class StatementPrepareFlagRawValueTests { @Test("prepare compiles SQL without tail") func prepareCompilesSQLWithoutTail() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: database) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.finalize() == .ok) } @@ -70,7 +73,7 @@ final class StatementPrepareFlagRawValueTests { @available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) func prepareWithFlagsCompilesSQL() throws { var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: database, prepareFlag: [.persistent]) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection, prepareFlag: [.persistent]) == .ok) let prepared = try #require(statement) #expect(prepared.finalize() == .ok) } diff --git a/Tests/LSQLiteTests/Statement/Statement+ReadonlyTests.swift b/Tests/LSQLiteTests/Statement/Statement+ReadonlyTests.swift index 21f8fbc..537b17e 100644 --- a/Tests/LSQLiteTests/Statement/Statement+ReadonlyTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+ReadonlyTests.swift @@ -5,23 +5,25 @@ import Testing struct StatementReadonlyTests { @Test("isReadonly reflects statement type") func isReadonlyReflectsStatementType() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) - try #require(openDatabase.exec("CREATE TABLE items(id INTEGER)") == .ok) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() + try #require(connection.exec("CREATE TABLE items(id INTEGER)") == .ok) var selectStatement: Statement? - try #require(Statement.prepare(&selectStatement, sql: "SELECT id FROM items", for: openDatabase) == .ok) + try #require(Statement.prepare(&selectStatement, sql: "SELECT id FROM items", for: connection) == .ok) let selectPrepared = try #require(selectStatement) #expect(selectPrepared.isReadonly) #expect(selectPrepared.finalize() == .ok) var insertStatement: Statement? - try #require(Statement.prepare(&insertStatement, sql: "INSERT INTO items(id) VALUES (1)", for: openDatabase) == .ok) + try #require(Statement.prepare(&insertStatement, sql: "INSERT INTO items(id) VALUES (1)", for: connection) == .ok) let insertPrepared = try #require(insertStatement) #expect(!insertPrepared.isReadonly) #expect(insertPrepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Statement/Statement+ResetTests.swift b/Tests/LSQLiteTests/Statement/Statement+ResetTests.swift index 6742050..59f68e3 100644 --- a/Tests/LSQLiteTests/Statement/Statement+ResetTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+ResetTests.swift @@ -5,18 +5,20 @@ import Testing struct StatementResetTests { @Test("reset allows re-stepping the statement") func resetAllowsReStepping() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .done) #expect(prepared.reset() == .ok) #expect(prepared.step() == .row) #expect(prepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Statement/Statement+SQLTests.swift b/Tests/LSQLiteTests/Statement/Statement+SQLTests.swift index a2360e8..d6f63d8 100644 --- a/Tests/LSQLiteTests/Statement/Statement+SQLTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+SQLTests.swift @@ -5,32 +5,36 @@ import Testing struct StatementSQLTests { @Test("sql returns original statement text") func sqlReturnsOriginalStatementText() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ?1", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ?1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.sql == "SELECT ?1") #expect(prepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } @Test("expandedSql includes bound values") @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) func expandedSqlIncludesBoundValues() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT ?1", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT ?1", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindInt(5, at: 1) == .ok) let expanded = prepared.expandedSql #expect(expanded?.contains("5") == true) #expect(prepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Statement/Statement+StepTests.swift b/Tests/LSQLiteTests/Statement/Statement+StepTests.swift index f309385..d7f9593 100644 --- a/Tests/LSQLiteTests/Statement/Statement+StepTests.swift +++ b/Tests/LSQLiteTests/Statement/Statement+StepTests.swift @@ -5,17 +5,19 @@ import Testing struct StatementStepTests { @Test("step advances through rows") func stepAdvancesThroughRows() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT 1 UNION ALL SELECT 2", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT 1 UNION ALL SELECT 2", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.step() == .row) #expect(prepared.step() == .row) #expect(prepared.step() == .done) #expect(prepared.finalize() == .ok) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Value/Value+GettersTests.swift b/Tests/LSQLiteTests/Value/Value+GettersTests.swift index cad95ca..5795bc6 100644 --- a/Tests/LSQLiteTests/Value/Value+GettersTests.swift +++ b/Tests/LSQLiteTests/Value/Value+GettersTests.swift @@ -5,16 +5,18 @@ import Testing struct ValueGettersTests { @Test("value getters expose underlying data") func valueGettersExposeUnderlyingData() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() let probe = ValueGetterProbe() let userData = Unmanaged.passUnretained(probe).toOpaque() - #expect(openDatabase.createFunction(name: "value_getters", argumentCount: 5, textEncoding: .utf8, userData: userData, funcHandler: valueGetterHandler) == .ok) + #expect(connection.createFunction(name: "value_getters", argumentCount: 5, textEncoding: .utf8, userData: userData, funcHandler: valueGetterHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT value_getters(?1, ?2, ?3, ?4, ?5)", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT value_getters(?1, ?2, ?3, ?4, ?5)", for: connection) == .ok) let prepared = try #require(statement) let blobBytes: [UInt8] = [0x01, 0x02, 0x03] @@ -36,21 +38,23 @@ struct ValueGettersTests { #expect(probe.intValue == 7) #expect(probe.int64Value == 9_000_000_000) #expect(probe.textValue == "text") - _ = openDatabase.close() + _ = connection.close() } @Test("text getter returns nil for NULL values") func textGetterReturnsNilForNullValues() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() let probe = ValueGetterProbe() let userData = Unmanaged.passUnretained(probe).toOpaque() - #expect(openDatabase.createFunction(name: "value_getters", argumentCount: 5, textEncoding: .utf8, userData: userData, funcHandler: valueGetterHandler) == .ok) + #expect(connection.createFunction(name: "value_getters", argumentCount: 5, textEncoding: .utf8, userData: userData, funcHandler: valueGetterHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT value_getters(?1, ?2, ?3, ?4, ?5)", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT value_getters(?1, ?2, ?3, ?4, ?5)", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindInt(1, at: 1) == .ok) @@ -64,7 +68,7 @@ struct ValueGettersTests { #expect(prepared.finalize() == .ok) #expect(probe.textValue == nil) - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Value/Value+IntrospectTests.swift b/Tests/LSQLiteTests/Value/Value+IntrospectTests.swift index 7adde90..23d446c 100644 --- a/Tests/LSQLiteTests/Value/Value+IntrospectTests.swift +++ b/Tests/LSQLiteTests/Value/Value+IntrospectTests.swift @@ -5,15 +5,17 @@ import Testing struct ValueIntrospectTests { @Test("value introspection reports metadata") func valueIntrospectionReportsMetadata() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var probe = ValueIntrospectProbe() - #expect(openDatabase.createFunction(name: "value_introspect", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: valueIntrospectHandler) == .ok) + #expect(connection.createFunction(name: "value_introspect", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: valueIntrospectHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT value_introspect(?1)", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT value_introspect(?1)", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindText("123", at: 1) == .ok) #expect(prepared.step() == .row) @@ -26,7 +28,7 @@ struct ValueIntrospectTests { if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) { #expect(probe.isFromBind) } - _ = openDatabase.close() + _ = connection.close() } } diff --git a/Tests/LSQLiteTests/Value/Value+MemoryTests.swift b/Tests/LSQLiteTests/Value/Value+MemoryTests.swift index 86ae7c4..7a08a66 100644 --- a/Tests/LSQLiteTests/Value/Value+MemoryTests.swift +++ b/Tests/LSQLiteTests/Value/Value+MemoryTests.swift @@ -6,15 +6,17 @@ struct ValueMemoryTests { @Test("createCopy and free round-trip values") @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) func createCopyAndFreeRoundTripValues() throws { - var database: Database? - try #require(Database.open(&database, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) - let openDatabase = try #require(database) + let connection: Connection = try { + var connection: Connection? + try #require(Connection.open(&connection, at: .memory, withOpenFlags: [.readwrite, .create]) == .ok) + return try #require(connection) + }() var probe = ValueMemoryProbe() - #expect(openDatabase.createFunction(name: "value_copy", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: valueCopyHandler) == .ok) + #expect(connection.createFunction(name: "value_copy", argumentCount: 1, textEncoding: .utf8, userData: &probe, funcHandler: valueCopyHandler) == .ok) var statement: Statement? - try #require(Statement.prepare(&statement, sql: "SELECT value_copy(?1)", for: openDatabase) == .ok) + try #require(Statement.prepare(&statement, sql: "SELECT value_copy(?1)", for: connection) == .ok) let prepared = try #require(statement) #expect(prepared.bindInt(99, at: 1) == .ok) #expect(prepared.step() == .row) @@ -22,7 +24,7 @@ struct ValueMemoryTests { #expect(prepared.finalize() == .ok) #expect(probe.copied) - _ = openDatabase.close() + _ = connection.close() } }