From 02120149ae3f30c9e5916c3737522bba41da25cc Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 18:28:14 +0200 Subject: [PATCH 1/6] Update description and debugDescription for all RawRepresentable types but OptionSet typs --- Sources/LSQLite/Database/Database+Busy.swift | 17 ++++--- .../Database/Database+Checkpoint.swift | 25 ++++++---- Sources/LSQLite/Database/Database+Error.swift | 17 ++++--- Sources/LSQLite/Database/Database+Exec.swift | 17 ++++--- Sources/LSQLite/Database/Database+Hooks.swift | 39 +++++++++------ Sources/LSQLite/Database/Database+Limit.swift | 49 ++++++++++++------- .../Database/Database+ProgressHandler.swift | 17 ++++--- .../LSQLite/Database/Database+Readonly.swift | 19 +++---- Sources/LSQLite/Database/Database+Trace.swift | 16 +++--- 9 files changed, 127 insertions(+), 89 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Busy.swift b/Sources/LSQLite/Database/Database+Busy.swift index f2ac290..8090372 100644 --- a/Sources/LSQLite/Database/Database+Busy.swift +++ b/Sources/LSQLite/Database/Database+Busy.swift @@ -9,7 +9,7 @@ extension Database { /// Result codes a busy handler can return to retry or abort. /// /// Related SQLite: `sqlite3_busy_handler`, `sqlite3_busy_timeout`, `SQLITE_BUSY` - @frozen public struct BusyHandlerResult: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct BusyHandlerResult: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -19,16 +19,17 @@ extension Database { public static let `break` = Self(rawValue: 0) public static let `continue` = Self(rawValue: 1) - /// Debug label for the busy handler response. - /// - /// Related SQLite: `sqlite3_busy_handler`, `SQLITE_BUSY` - public var debugDescription: String { + public var description: String { switch self { - case .break: return "LSQLITE_BREAK" - case .continue: return "LSQLITE_CONTINUE" - default: return "BusyHandlerResult(rawValue: \(rawValue))" + case .break: "break" + case .continue: "continue" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Registers a busy handler invoked when this connection hits `SQLITE_BUSY`; return `.continue` to retry or `.break` to stop. diff --git a/Sources/LSQLite/Database/Database+Checkpoint.swift b/Sources/LSQLite/Database/Database+Checkpoint.swift index c42b95f..98f2eae 100644 --- a/Sources/LSQLite/Database/Database+Checkpoint.swift +++ b/Sources/LSQLite/Database/Database+Checkpoint.swift @@ -4,7 +4,7 @@ extension Database { /// 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` - @frozen public struct CheckpointMode: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct CheckpointMode: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -16,16 +16,23 @@ extension Database { public static let restart = Self(rawValue: SQLITE_CHECKPOINT_RESTART) public static let truncate = Self(rawValue: SQLITE_CHECKPOINT_TRUNCATE) - /// Debug label for the checkpoint mode. - /// - /// Related SQLite: `SQLITE_CHECKPOINT_PASSIVE`, `SQLITE_CHECKPOINT_FULL`, `SQLITE_CHECKPOINT_RESTART`, `SQLITE_CHECKPOINT_TRUNCATE` + public var description: String { + switch self { + case .passive: "passive" + case .full: "full" + case .restart: "restart" + case .truncate: "truncate" + default: "unknown" + } + } + public var debugDescription: String { switch self { - case .passive: return "SQLITE_CHECKPOINT_PASSIVE" - case .full: return "SQLITE_CHECKPOINT_FULL" - case .restart: return "SQLITE_CHECKPOINT_RESTART" - case .truncate: return "SQLITE_CHECKPOINT_TRUNCATE" - default: return "CheckpointMode(rawValue: \(rawValue))" + case .passive: "SQLITE_CHECKPOINT_PASSIVE" + case .full: "SQLITE_CHECKPOINT_FULL" + case .restart: "SQLITE_CHECKPOINT_RESTART" + case .truncate: "SQLITE_CHECKPOINT_TRUNCATE" + default: rawValue.description } } } diff --git a/Sources/LSQLite/Database/Database+Error.swift b/Sources/LSQLite/Database/Database+Error.swift index 0a4574e..fe193fa 100644 --- a/Sources/LSQLite/Database/Database+Error.swift +++ b/Sources/LSQLite/Database/Database+Error.swift @@ -4,7 +4,7 @@ extension Database { /// Switch controlling whether extended result codes are reported for this connection. /// /// Related SQLite: `sqlite3_extended_result_codes` - @frozen public struct ExtendedResultCodeStatus: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct ExtendedResultCodeStatus: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -14,16 +14,17 @@ extension Database { public static let off = Self(rawValue: 0) public static let on = Self(rawValue: 1) - /// Debug label for the extended result code toggle. - /// - /// Related SQLite: `sqlite3_extended_result_codes` - public var debugDescription: String { + public var description: String { switch self { - case .off: return "LSQLITE_OFF" - case .on: return "LSQLITE_ON" - default: return "ExtendedResultCodeStatus(rawValue: \(rawValue))" + case .off: "off" + case .on: "on" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Last result code set by an API call on this connection. diff --git a/Sources/LSQLite/Database/Database+Exec.swift b/Sources/LSQLite/Database/Database+Exec.swift index 54fea5c..f452c2b 100644 --- a/Sources/LSQLite/Database/Database+Exec.swift +++ b/Sources/LSQLite/Database/Database+Exec.swift @@ -9,7 +9,7 @@ extension Database { /// Return codes for `ExecCallback` indicating whether to continue or abort execution. /// /// Related SQLite: `sqlite3_exec` - @frozen public struct ExecCallbackResult: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct ExecCallbackResult: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -19,16 +19,17 @@ extension Database { public static let `continue` = Self(rawValue: 0) public static let abort = Self(rawValue: 1) - /// Debug label for the exec callback result. - /// - /// Related SQLite: `sqlite3_exec` - public var debugDescription: String { + public var description: String { switch self { - case .continue: return "LSQLITE_CONTINUE" - case .abort: return "LSQLITE_ABORT" - default: return "ExecCallbackResult(rawValue: \(rawValue))" + case .continue: "continue" + case .abort: "abort" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Executes one or more semicolon-separated SQL statements with an optional row callback. diff --git a/Sources/LSQLite/Database/Database+Hooks.swift b/Sources/LSQLite/Database/Database+Hooks.swift index 96613d2..354d7c3 100644 --- a/Sources/LSQLite/Database/Database+Hooks.swift +++ b/Sources/LSQLite/Database/Database+Hooks.swift @@ -24,7 +24,7 @@ extension Database { /// Return codes for commit hooks to continue or force a rollback. /// /// Related SQLite: `sqlite3_commit_hook` - @frozen public struct CommitHookHandlerResult: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct CommitHookHandlerResult: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -34,22 +34,23 @@ extension Database { public static let `continue` = Self(rawValue: 0) public static let `break` = Self(rawValue: 1) - /// Debug label for the commit hook decision. - /// - /// Related SQLite: `sqlite3_commit_hook` - public var debugDescription: String { + public var description: String { switch self { - case .continue: return "LSQLITE_CONTINUE" - case .break: return "LSQLITE_BREAK" - default: return "CommitHookHandlerResult(rawValue: \(rawValue))" + case .continue: "continue" + case .break: "break" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Operation types reported to `updateHook`. /// /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` - @frozen public struct UpdateOperation: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct UpdateOperation: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -60,15 +61,21 @@ extension Database { public static let insert = Self(rawValue: SQLITE_INSERT) public static let update = Self(rawValue: SQLITE_UPDATE) - /// Debug label for the update operation. - /// - /// Related SQLite: `sqlite3_update_hook`, `SQLITE_INSERT`, `SQLITE_DELETE`, `SQLITE_UPDATE` + public var description: String { + switch self { + case .delete: "delete" + case .insert: "insert" + case .update: "update" + default: "unknown" + } + } + public var debugDescription: String { switch self { - case .delete: return "SQLITE_DELETE" - case .insert: return "SQLITE_INSERT" - case .update: return "SQLITE_UPDATE" - default: return "UpdateOperation(rawValue: \(rawValue))" + case .delete: "SQLITE_DELETE" + case .insert: "SQLITE_INSERT" + case .update: "SQLITE_UPDATE" + default: rawValue.description } } } diff --git a/Sources/LSQLite/Database/Database+Limit.swift b/Sources/LSQLite/Database/Database+Limit.swift index aacdf46..8f3dd5b 100644 --- a/Sources/LSQLite/Database/Database+Limit.swift +++ b/Sources/LSQLite/Database/Database+Limit.swift @@ -4,7 +4,7 @@ extension Database { /// Runtime limit categories used with `Database.limit(for:)` and `Database.setLimit(_:for:)`. /// /// Related SQLite: `sqlite3_limit`, `SQLITE_LIMIT_*` - @frozen public struct LimitCategory: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct LimitCategory: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -60,24 +60,39 @@ extension Database { /// Related SQLite: `SQLITE_LIMIT_WORKER_THREADS` public static let workerThreads = Self(rawValue: SQLITE_LIMIT_WORKER_THREADS) - /// Debug label for the limit category. - /// - /// Related SQLite: `SQLITE_LIMIT_*` + public var description: String { + switch self { + case .length: "length" + case .sqlLength: "sql length" + case .column: "column" + case .exprDepth: "expression depth" + case .compoundSelect: "compound select" + case .vdbeOp: "vdbe op" + case .functionArg: "function arg" + case .attached: "attached" + case .likePatternLength: "like pattern length" + case .variableNumber: "variable number" + case .triggerDepth: "trigger depth" + case .workerThreads: "worker threads" + default: "unknown" + } + } + public var debugDescription: String { switch self { - case .length: return "SQLITE_LIMIT_LENGTH" - case .sqlLength: return "SQLITE_LIMIT_SQL_LENGTH)" - case .column: return "SQLITE_LIMIT_COLUMN)" - case .exprDepth: return "SQLITE_LIMIT_EXPR_DEPTH)" - case .compoundSelect: return "SQLITE_LIMIT_COMPOUND_SELECT)" - case .vdbeOp: return "SQLITE_LIMIT_VDBE_OP)" - case .functionArg: return "SQLITE_LIMIT_FUNCTION_ARG)" - case .attached: return "SQLITE_LIMIT_ATTACHED)" - case .likePatternLength: return "SQLITE_LIMIT_LIKE_PATTERN_LENGTH)" - case .variableNumber: return "SQLITE_LIMIT_VARIABLE_NUMBER)" - case .triggerDepth: return "SQLITE_LIMIT_TRIGGER_DEPTH)" - case .workerThreads: return "SQLITE_LIMIT_WORKER_THREADS)" - default: return "LimitCategory(rawValue: \(rawValue))" + case .length: "SQLITE_LIMIT_LENGTH" + case .sqlLength: "SQLITE_LIMIT_SQL_LENGTH" + case .column: "SQLITE_LIMIT_COLUMN" + case .exprDepth: "SQLITE_LIMIT_EXPR_DEPTH" + case .compoundSelect: "SQLITE_LIMIT_COMPOUND_SELECT" + case .vdbeOp: "SQLITE_LIMIT_VDBE_OP" + case .functionArg: "SQLITE_LIMIT_FUNCTION_ARG" + case .attached: "SQLITE_LIMIT_ATTACHED" + case .likePatternLength: "SQLITE_LIMIT_LIKE_PATTERN_LENGTH" + case .variableNumber: "SQLITE_LIMIT_VARIABLE_NUMBER" + case .triggerDepth: "SQLITE_LIMIT_TRIGGER_DEPTH" + case .workerThreads: "SQLITE_LIMIT_WORKER_THREADS" + default: rawValue.description } } } diff --git a/Sources/LSQLite/Database/Database+ProgressHandler.swift b/Sources/LSQLite/Database/Database+ProgressHandler.swift index c51b707..fa4e247 100644 --- a/Sources/LSQLite/Database/Database+ProgressHandler.swift +++ b/Sources/LSQLite/Database/Database+ProgressHandler.swift @@ -9,7 +9,7 @@ extension Database { /// Return codes from a progress handler to keep running or interrupt. /// /// Related SQLite: `sqlite3_progress_handler`, `SQLITE_INTERRUPT` - @frozen public struct ProgressHandlerResult: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct ProgressHandlerResult: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -19,16 +19,17 @@ extension Database { public static let `continue` = Self(rawValue: 0) public static let interrupt = Self(rawValue: 1) - /// Debug label for the progress handler result. - /// - /// Related SQLite: `sqlite3_progress_handler`, `SQLITE_INTERRUPT` - public var debugDescription: String { + public var description: String { switch self { - case .continue: return "LSQLITE_CONTINUE" - case .interrupt: return "LSQLITE_INTERRUPT" - default: return "ProgressHandlerResult(rawValue: \(rawValue))" + case .continue: "continue" + case .interrupt: "interrupt" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Registers a progress handler invoked every `instructionCount` virtual machine steps; return `.interrupt` to halt the operation. diff --git a/Sources/LSQLite/Database/Database+Readonly.swift b/Sources/LSQLite/Database/Database+Readonly.swift index fd99c37..58e4c64 100644 --- a/Sources/LSQLite/Database/Database+Readonly.swift +++ b/Sources/LSQLite/Database/Database+Readonly.swift @@ -4,7 +4,7 @@ extension Database { /// Read/write state values returned by `readWriteAccessState(forDatabaseNamed:)`. /// /// Related SQLite: `sqlite3_db_readonly` - @frozen public struct ReadWriteAccessState: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct ReadWriteAccessState: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -15,17 +15,18 @@ extension Database { public static let readwrite = Self(rawValue: 0) public static let readonly = Self(rawValue: 1) - /// Debug label for the access state. - /// - /// Related SQLite: `sqlite3_db_readonly` - public var debugDescription: String { + public var description: String { switch self { - case .noDatabase: return "LSQLITE_NO_DATABASE" - case .readwrite: return "LSQLITE_READWRITE)" - case .readonly: return "LSQLITE_READONLY)" - default: return "ReadWriteAccessState(rawValue: \(rawValue))" + case .noDatabase: "no database" + case .readwrite: "readwrite" + case .readonly: "readonly" + default: "unknown" } } + + public var debugDescription: String { + "\(description) (\(rawValue.description))" + } } /// Reports whether the named database is read-only, read/write, or missing on this connection. diff --git a/Sources/LSQLite/Database/Database+Trace.swift b/Sources/LSQLite/Database/Database+Trace.swift index 0873a9a..3afdcf0 100644 --- a/Sources/LSQLite/Database/Database+Trace.swift +++ b/Sources/LSQLite/Database/Database+Trace.swift @@ -10,7 +10,7 @@ extension Database { /// Return code expected from a trace callback. /// /// Related SQLite: `sqlite3_trace_v2` - @frozen public struct TraceEventCallbackResult: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct TraceEventCallbackResult: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -19,13 +19,17 @@ extension Database { public static let ok = Self(rawValue: 0) - /// Debug label for the trace callback result. - /// - /// Related SQLite: `sqlite3_trace_v2` + public var description: String { + switch self { + case .ok: "ok" + default: "unknown" + } + } + public var debugDescription: String { switch self { - case .ok: return "LSQLITE_OK" - default: return "TraceEventCallbackResult(rawValue: \(rawValue))" + case .ok: "SQLITE_OK" + default: rawValue.description } } } From 215878a62a1e2429d0713d28ab813663c3f4bc26 Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 19:02:41 +0200 Subject: [PATCH 2/6] Fix flags for creating functions --- .../LSQLite/Database/Database+Function.swift | 121 +++++++++++++++--- Tests/LSQLiteTests/FunctionTests.swift | 4 +- 2 files changed, 105 insertions(+), 20 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Database/Database+Function.swift index a7b0c54..489a52f 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Database/Database+Function.swift @@ -31,36 +31,117 @@ extension Database { /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function` public typealias FunctionDestroyHandler = @convention(c) (_ userData: UnsafeMutableRawPointer?) -> Void - /// Options describing text encoding and determinism for user-defined SQL functions. + /// Preferred text encoding for user-defined SQL functions. /// - /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` - @frozen public struct FunctionFlag: Hashable, OptionSet, CustomDebugStringConvertible { + /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_function`, `sqlite3_create_function16`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_ANY`, `SQLITE_UTF16_ALIGNED` + @frozen public struct TextEncoding: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { self.rawValue = rawValue } + /// UTF-8 text encoding. + /// + /// Related SQLite: `SQLITE_UTF8` public static let utf8 = Self(rawValue: SQLITE_UTF8) + + /// UTF-16 little-endian text encoding. + /// + /// Related SQLite: `SQLITE_UTF16LE` public static let utf16le = Self(rawValue: SQLITE_UTF16LE) + + /// UTF-16 big-endian text encoding. + /// + /// Related SQLite: `SQLITE_UTF16BE` public static let utf16be = Self(rawValue: SQLITE_UTF16BE) + + /// UTF-16 text encoding using native byte order. + /// + /// Related SQLite: `SQLITE_UTF16` public static let utf16 = Self(rawValue: SQLITE_UTF16) + + /// Deprecated encoding sentinel that lets SQLite choose a text representation. + /// + /// Related SQLite: `SQLITE_ANY` public static let any = Self(rawValue: SQLITE_ANY) + + /// UTF-16 encoding that requires 2-byte alignment. + /// + /// Related SQLite: `SQLITE_UTF16_ALIGNED` public static let utf16Aligned = Self(rawValue: SQLITE_UTF16_ALIGNED) + public var description: String { + switch self { + case .utf8: "utf8" + case .utf16le: "utf16le" + case .utf16be: "utf16be" + case .utf16: "utf16" + case .any: "any" + case .utf16Aligned: "utf16 aligned" + default: "unknown" + } + } + + public var debugDescription: String { + switch self { + case .utf8: "SQLITE_UTF8" + case .utf16le: "SQLITE_UTF16LE" + case .utf16be: "SQLITE_UTF16BE" + case .utf16: "SQLITE_UTF16" + case .any: "SQLITE_ANY" + case .utf16Aligned: "SQLITE_UTF16_ALIGNED" + default: rawValue.description + } + } + } + + /// Function flags describing determinism and security properties for user-defined SQL functions. + /// + /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` + @frozen public struct FunctionFlag: Hashable, OptionSet, CustomDebugStringConvertible { + public let rawValue: Int32 + + @inlinable public init(rawValue: Int32) { + self.rawValue = rawValue + } + + /// Marks a function as deterministic so SQLite can apply optimizations. + /// + /// Related SQLite: `SQLITE_DETERMINISTIC` public static let deterministic = Self(rawValue: SQLITE_DETERMINISTIC) + /// Restricts the function to top-level SQL only. + /// + /// Related SQLite: `SQLITE_DIRECTONLY` + public static let directOnly = Self(rawValue: SQLITE_DIRECTONLY) + /// Indicates the function inspects argument subtypes via `sqlite3_value_subtype`. + /// + /// Related SQLite: `SQLITE_SUBTYPE` + public static let subtype = Self(rawValue: SQLITE_SUBTYPE) + /// Marks the function as innocuous for trusted schema checks. + /// + /// Related SQLite: `SQLITE_INNOCUOUS` + public static let innocuous = Self(rawValue: SQLITE_INNOCUOUS) + /// Indicates the function may call `sqlite3_result_subtype`. + /// + /// Related SQLite: `SQLITE_RESULT_SUBTYPE` + public static let resultSubtype = Self(rawValue: SQLITE_RESULT_SUBTYPE) + /// Declares an ordered-set aggregate that sorts its first argument. + /// + /// Related SQLite: `SQLITE_SELFORDER1` + public static let selfOrder1 = Self(rawValue: SQLITE_SELFORDER1) /// Debug label for the function flag value. /// - /// Related SQLite: `sqlite3_create_function_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` + /// Related SQLite: `sqlite3_create_function_v2`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` public var debugDescription: String { switch self { - case .utf8: return "SQLITE_UTF8" - case .utf16le: return "SQLITE_UTF16LE" - case .utf16be: return "SQLITE_UTF16BE" - case .utf16: return "SQLITE_UTF16" - case .any: return "SQLITE_ANY" - case .utf16Aligned: return "SQLITE_UTF16_ALIGNED" + case .deterministic: return "SQLITE_DETERMINISTIC" + case .directOnly: return "SQLITE_DIRECTONLY" + case .subtype: return "SQLITE_SUBTYPE" + case .innocuous: return "SQLITE_INNOCUOUS" + case .resultSubtype: return "SQLITE_RESULT_SUBTYPE" + case .selfOrder1: return "SQLITE_SELFORDER1" default: return "FunctionFlag(rawValue: \(rawValue))" } } @@ -70,7 +151,8 @@ extension Database { /// - Parameters: /// - name: Function name (UTF-8). /// - argumentCount: Allowed argument count; `-1` permits variable arguments. - /// - flag: Encoding and determinism options. + /// - textEncoding: Preferred text encoding for the function. + /// - flags: Function behavior flags. /// - userData: Context pointer available via `Context.userData`. /// - funcHandler: Scalar implementation; set when defining a scalar function. /// - stepHandler: Aggregate step implementation; set when defining an aggregate or window function. @@ -78,16 +160,18 @@ extension Database { /// - destroyHandler: Optional cleanup for `userData` when the function is replaced or the connection closes. /// - Returns: Result of `sqlite3_create_function_v2`. /// - /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_user_data`, `SQLITE_DETERMINISTIC`, `SQLITE_LIMIT_FUNCTION_ARG` - @inlinable public func createFunction(name: String, argumentCount: Int32, flags flag: FunctionFlag, userData: UnsafeMutableRawPointer? = nil, funcHandler: FunctionFuncHandler? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { - sqlite3_create_function_v2(rawValue, name, argumentCount, flag.rawValue, userData, funcHandler, stepHandler, finalHandler, destroyHandler).resultCode + /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_user_data`, `SQLITE_UTF8`, `SQLITE_DETERMINISTIC`, `SQLITE_LIMIT_FUNCTION_ARG` + @inlinable public func createFunction(name: String, argumentCount: Int32, textEncoding: TextEncoding, flags: FunctionFlag = [], userData: UnsafeMutableRawPointer? = nil, funcHandler: FunctionFuncHandler? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { + let combinedFlags = textEncoding.rawValue | flags.rawValue + return sqlite3_create_function_v2(rawValue, name, argumentCount, combinedFlags, userData, funcHandler, stepHandler, finalHandler, destroyHandler).resultCode } /// Registers or redefines an aggregate or window function on this connection. /// - Parameters: /// - name: Function name (UTF-8). /// - argumentCount: Allowed argument count; `-1` permits variable arguments. - /// - flag: Encoding and determinism options. + /// - textEncoding: Preferred text encoding for the function. + /// - flags: Function behavior flags. /// - userData: Context pointer available via `Context.userData`. /// - stepHandler: Aggregate step implementation (required). /// - finalHandler: Aggregate finalizer (required). @@ -96,9 +180,10 @@ extension Database { /// - destroyHandler: Optional cleanup for `userData` when the function is dropped. /// - Returns: Result of `sqlite3_create_window_function`. /// - /// Related SQLite: `sqlite3_create_window_function`, `sqlite3_create_function_v2`, `sqlite3_user_data` + /// Related SQLite: `sqlite3_create_window_function`, `sqlite3_create_function_v2`, `sqlite3_user_data`, `SQLITE_UTF8` @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) - @inlinable public func createWindowFunction(name: String, argumentCount: Int32, flags flag: FunctionFlag, userData: UnsafeMutableRawPointer? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, valueHandler: FunctionValueHandler? = nil, inverseHandler: FunctionInverseHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { - sqlite3_create_window_function(rawValue, name, argumentCount, flag.rawValue, userData, stepHandler, finalHandler, valueHandler, inverseHandler, destroyHandler).resultCode + @inlinable public func createWindowFunction(name: String, argumentCount: Int32, textEncoding: TextEncoding, flags: FunctionFlag = [], userData: UnsafeMutableRawPointer? = nil, stepHandler: FunctionStepHandler? = nil, finalHandler: FunctionFinalHandler? = nil, valueHandler: FunctionValueHandler? = nil, inverseHandler: FunctionInverseHandler? = nil, destroyHandler: FunctionDestroyHandler? = nil) -> ResultCode { + let combinedFlags = textEncoding.rawValue | flags.rawValue + return sqlite3_create_window_function(rawValue, name, argumentCount, combinedFlags, userData, stepHandler, finalHandler, valueHandler, inverseHandler, destroyHandler).resultCode } } diff --git a/Tests/LSQLiteTests/FunctionTests.swift b/Tests/LSQLiteTests/FunctionTests.swift index 0f43219..9c17ae3 100644 --- a/Tests/LSQLiteTests/FunctionTests.swift +++ b/Tests/LSQLiteTests/FunctionTests.swift @@ -18,12 +18,12 @@ class FunctionTests: XCTestCase { } return database }() - let myFiveResultCode = database.createFunction(name: "my_five", argumentCount: 0, flags: .utf8, userData: nil, funcHandler: { context, _, _ in + let myFiveResultCode = database.createFunction(name: "my_five", argumentCount: 0, textEncoding: .utf8, userData: nil, funcHandler: { context, _, _ in let context = Context(rawValue: context!) context.resultInt(5) }) XCTAssertEqual(myFiveResultCode, .ok) - let myCountResultCode = database.createFunction(name: "my_count", argumentCount: 0, flags: .utf8, userData: nil, stepHandler: { context, _, _ in + let myCountResultCode = database.createFunction(name: "my_count", argumentCount: 0, textEncoding: .utf8, userData: nil, stepHandler: { context, _, _ in let context = Context(rawValue: context!) let aggregatePointer = context.aggregateContext(size: Int32(MemoryLayout.stride)) guard let valuePointer = aggregatePointer?.assumingMemoryBound(to: Int32.self) else { From 1ac6c39e66f16317f4b2b446b97535e62d6e05eb Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 19:29:28 +0200 Subject: [PATCH 3/6] Update function and collation flags --- .../Database/Database+Authorizer.swift | 4 +-- .../LSQLite/Database/Database+Collation.swift | 28 +++++++++++-------- .../LSQLite/Database/Database+Function.swift | 7 ----- Sources/LSQLite/Database/Database+Limit.swift | 11 ++++++++ Sources/LSQLite/Database/Database+Open.swift | 6 +++- Sources/LSQLite/Database/Database+Trace.swift | 11 +++----- Sources/LSQLite/Datatype.swift | 2 +- Sources/LSQLite/ResultCode.swift | 2 +- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Authorizer.swift b/Sources/LSQLite/Database/Database+Authorizer.swift index 3abd9b9..cb2aad0 100644 --- a/Sources/LSQLite/Database/Database+Authorizer.swift +++ b/Sources/LSQLite/Database/Database+Authorizer.swift @@ -48,7 +48,7 @@ extension Database { case .ok: "ok" case .deny: "deny" case .ignore: "ignore" - default: rawValue.description + default: "unknown" } } @@ -182,7 +182,7 @@ extension Database { case .savepoint: "savepoint" case .copy: "copy" case .recursive: "recursive" - default: rawValue.description + default: "unknown" } } diff --git a/Sources/LSQLite/Database/Database+Collation.swift b/Sources/LSQLite/Database/Database+Collation.swift index f572ff1..8d13f39 100644 --- a/Sources/LSQLite/Database/Database+Collation.swift +++ b/Sources/LSQLite/Database/Database+Collation.swift @@ -19,7 +19,7 @@ extension Database { /// Text encoding and behavior flags for user-defined collations. /// /// Related SQLite: `sqlite3_create_collation_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` - @frozen public struct CollationFlag: Hashable, RawRepresentable, CustomDebugStringConvertible { + @frozen public struct CollationFlag: Hashable, RawRepresentable, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -32,19 +32,25 @@ extension Database { public static let utf16 = Self(rawValue: SQLITE_UTF16) public static let utf16Aligned = Self(rawValue: SQLITE_UTF16_ALIGNED) - public static let deterministic = Self(rawValue: SQLITE_DETERMINISTIC) + public var description: String { + switch self { + case .utf8: "utf8" + case .utf16le: "utf16le" + case .utf16be: "utf16be" + case .utf16: "utf16" + case .utf16Aligned: "utf16Aligned" + default: "unknown" + } + } - /// Debug label for the collation flag. - /// - /// Related SQLite: `sqlite3_create_collation_v2`, `SQLITE_UTF8`, `SQLITE_UTF16LE`, `SQLITE_UTF16BE`, `SQLITE_UTF16`, `SQLITE_UTF16_ALIGNED`, `SQLITE_DETERMINISTIC` public var debugDescription: String { switch self { - case .utf8: return "SQLITE_UTF8" - case .utf16le: return "SQLITE_UTF16LE" - case .utf16be: return "SQLITE_UTF16BE" - case .utf16: return "SQLITE_UTF16" - case .utf16Aligned: return "SQLITE_UTF16_ALIGNED" - default: return "FunctionFlag(rawValue: \(rawValue))" + case .utf8: "SQLITE_UTF8" + case .utf16le: "SQLITE_UTF16LE" + case .utf16be: "SQLITE_UTF16BE" + case .utf16: "SQLITE_UTF16" + case .utf16Aligned: "SQLITE_UTF16_ALIGNED" + default: rawValue.description } } } diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Database/Database+Function.swift index 489a52f..1a0cb9c 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Database/Database+Function.swift @@ -66,11 +66,6 @@ extension Database { /// Related SQLite: `SQLITE_ANY` public static let any = Self(rawValue: SQLITE_ANY) - /// UTF-16 encoding that requires 2-byte alignment. - /// - /// Related SQLite: `SQLITE_UTF16_ALIGNED` - public static let utf16Aligned = Self(rawValue: SQLITE_UTF16_ALIGNED) - public var description: String { switch self { case .utf8: "utf8" @@ -78,7 +73,6 @@ extension Database { case .utf16be: "utf16be" case .utf16: "utf16" case .any: "any" - case .utf16Aligned: "utf16 aligned" default: "unknown" } } @@ -90,7 +84,6 @@ extension Database { case .utf16be: "SQLITE_UTF16BE" case .utf16: "SQLITE_UTF16" case .any: "SQLITE_ANY" - case .utf16Aligned: "SQLITE_UTF16_ALIGNED" default: rawValue.description } } diff --git a/Sources/LSQLite/Database/Database+Limit.swift b/Sources/LSQLite/Database/Database+Limit.swift index 8f3dd5b..ac5ad29 100644 --- a/Sources/LSQLite/Database/Database+Limit.swift +++ b/Sources/LSQLite/Database/Database+Limit.swift @@ -15,46 +15,57 @@ extension Database { /// /// Related SQLite: `SQLITE_LIMIT_LENGTH` public static let length = Self(rawValue: SQLITE_LIMIT_LENGTH) + /// Maximum length of an SQL statement in bytes. /// /// Related SQLite: `SQLITE_LIMIT_SQL_LENGTH` public static let sqlLength = Self(rawValue: SQLITE_LIMIT_SQL_LENGTH) + /// Maximum number of columns in a table definition, result set, index, ORDER BY, or GROUP BY. /// /// Related SQLite: `SQLITE_LIMIT_COLUMN` public static let column = Self(rawValue: SQLITE_LIMIT_COLUMN) + /// Maximum expression parse-tree depth. /// /// Related SQLite: `SQLITE_LIMIT_EXPR_DEPTH` public static let exprDepth = Self(rawValue: SQLITE_LIMIT_EXPR_DEPTH) + /// Maximum terms in a compound SELECT. /// /// Related SQLite: `SQLITE_LIMIT_COMPOUND_SELECT` public static let compoundSelect = Self(rawValue: SQLITE_LIMIT_COMPOUND_SELECT) + /// Maximum opcodes in a single prepared statement VM program. /// /// Related SQLite: `SQLITE_LIMIT_VDBE_OP` public static let vdbeOp = Self(rawValue: SQLITE_LIMIT_VDBE_OP) + /// Maximum number of arguments on a function. /// /// Related SQLite: `SQLITE_LIMIT_FUNCTION_ARG` public static let functionArg = Self(rawValue: SQLITE_LIMIT_FUNCTION_ARG) + /// Maximum number of attached databases. /// /// Related SQLite: `SQLITE_LIMIT_ATTACHED` public static let attached = Self(rawValue: SQLITE_LIMIT_ATTACHED) + /// Maximum pattern length for LIKE or GLOB. /// /// Related SQLite: `SQLITE_LIMIT_LIKE_PATTERN_LENGTH` public static let likePatternLength = Self(rawValue: SQLITE_LIMIT_LIKE_PATTERN_LENGTH) + /// Maximum parameter index in a statement. /// /// Related SQLite: `SQLITE_LIMIT_VARIABLE_NUMBER` public static let variableNumber = Self(rawValue: SQLITE_LIMIT_VARIABLE_NUMBER) + /// Maximum trigger recursion depth. /// /// Related SQLite: `SQLITE_LIMIT_TRIGGER_DEPTH` public static let triggerDepth = Self(rawValue: SQLITE_LIMIT_TRIGGER_DEPTH) + /// Maximum auxiliary worker threads a prepared statement may start. /// /// Related SQLite: `SQLITE_LIMIT_WORKER_THREADS` diff --git a/Sources/LSQLite/Database/Database+Open.swift b/Sources/LSQLite/Database/Database+Open.swift index ae8539f..7033e63 100644 --- a/Sources/LSQLite/Database/Database+Open.swift +++ b/Sources/LSQLite/Database/Database+Open.swift @@ -6,7 +6,7 @@ extension Database { /// To construct a `file:` URI filename, use `Database.FileName.uri(...)` and open with `.uri` in the flags. /// /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_temp_directory`, `SQLITE_OPEN_URI` - @frozen public struct FileName: RawRepresentable { + @frozen public struct FileName: RawRepresentable, CustomStringConvertible { public let rawValue: String /// Creates a filename wrapper from a Swift string. @@ -25,6 +25,10 @@ extension Database { /// /// Related SQLite: `sqlite3_open`, `sqlite3_open_v2`, `sqlite3_temp_directory` public static let temporary = Self(rawValue: "") + + public var description: String { + rawValue.description + } } /// Flags passed to `open(_:at:withOpenFlags:)` and custom VFS xOpen calls. diff --git a/Sources/LSQLite/Database/Database+Trace.swift b/Sources/LSQLite/Database/Database+Trace.swift index 3afdcf0..091688a 100644 --- a/Sources/LSQLite/Database/Database+Trace.swift +++ b/Sources/LSQLite/Database/Database+Trace.swift @@ -27,10 +27,7 @@ extension Database { } public var debugDescription: String { - switch self { - case .ok: "SQLITE_OK" - default: rawValue.description - } + "\(description) (\(rawValue.description))" } } @@ -48,22 +45,22 @@ extension Database { /// /// Related SQLite: `SQLITE_TRACE_STMT`, `sqlite3_trace_v2`, `sqlite3_expanded_sql` public static let statement = Self(rawValue: UInt32(SQLITE_TRACE_STMT)) + /// Trace profiling information when a statement finishes; X points to elapsed nanoseconds. /// /// Related SQLite: `SQLITE_TRACE_PROFILE`, `sqlite3_profile`, `sqlite3_trace_v2` public static let profile = Self(rawValue: UInt32(SQLITE_TRACE_PROFILE)) + /// Trace each row produced by a prepared statement. /// /// Related SQLite: `SQLITE_TRACE_ROW`, `sqlite3_trace_v2` public static let row = Self(rawValue: UInt32(SQLITE_TRACE_ROW)) + /// Trace when the database connection closes. /// /// Related SQLite: `SQLITE_TRACE_CLOSE`, `sqlite3_trace_v2` public static let close = Self(rawValue: UInt32(SQLITE_TRACE_CLOSE)) - /// Debug label for the trace event code. - /// - /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` public var debugDescription: String { switch self { case .statement: return "SQLITE_TRACE_STMT" diff --git a/Sources/LSQLite/Datatype.swift b/Sources/LSQLite/Datatype.swift index a425b6c..50440ba 100644 --- a/Sources/LSQLite/Datatype.swift +++ b/Sources/LSQLite/Datatype.swift @@ -52,7 +52,7 @@ import MissedSwiftSQLite case .blob: "blob" case .null: "null" case .text: "text" - default: rawValue.description + default: "unknown" } } diff --git a/Sources/LSQLite/ResultCode.swift b/Sources/LSQLite/ResultCode.swift index d65579f..5ac6ce5 100644 --- a/Sources/LSQLite/ResultCode.swift +++ b/Sources/LSQLite/ResultCode.swift @@ -272,7 +272,7 @@ import MissedSwiftSQLite case .okLoadPermanently: "ok: load permanently" - default: rawValue.description + default: "unknown" } } From 3aaac3de10aefdd028dbaa3246647dca41c1c0e7 Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 19:33:45 +0200 Subject: [PATCH 4/6] Remove descriptions from OptionSet --- .../LSQLite/Database/Database+Function.swift | 17 +---------------- Sources/LSQLite/Database/Database+Trace.swift | 12 +----------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Database/Database+Function.swift index 1a0cb9c..8f0bf99 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Database/Database+Function.swift @@ -92,7 +92,7 @@ extension Database { /// Function flags describing determinism and security properties for user-defined SQL functions. /// /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` - @frozen public struct FunctionFlag: Hashable, OptionSet, CustomDebugStringConvertible { + @frozen public struct FunctionFlag: Hashable, OptionSet { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -123,21 +123,6 @@ extension Database { /// /// Related SQLite: `SQLITE_SELFORDER1` public static let selfOrder1 = Self(rawValue: SQLITE_SELFORDER1) - - /// Debug label for the function flag value. - /// - /// Related SQLite: `sqlite3_create_function_v2`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` - public var debugDescription: String { - switch self { - case .deterministic: return "SQLITE_DETERMINISTIC" - case .directOnly: return "SQLITE_DIRECTONLY" - case .subtype: return "SQLITE_SUBTYPE" - case .innocuous: return "SQLITE_INNOCUOUS" - case .resultSubtype: return "SQLITE_RESULT_SUBTYPE" - case .selfOrder1: return "SQLITE_SELFORDER1" - default: return "FunctionFlag(rawValue: \(rawValue))" - } - } } /// Registers or redefines a scalar or aggregate SQL function on this connection. diff --git a/Sources/LSQLite/Database/Database+Trace.swift b/Sources/LSQLite/Database/Database+Trace.swift index 091688a..c5e67ec 100644 --- a/Sources/LSQLite/Database/Database+Trace.swift +++ b/Sources/LSQLite/Database/Database+Trace.swift @@ -34,7 +34,7 @@ extension Database { /// Event mask values used when registering `setTraceCallback(for:userData:callback:)`. /// /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` - @frozen public struct TraceEventCode: Hashable, OptionSet, CustomDebugStringConvertible { + @frozen public struct TraceEventCode: Hashable, OptionSet { public let rawValue: UInt32 @inlinable public init(rawValue: UInt32) { @@ -60,16 +60,6 @@ extension Database { /// /// Related SQLite: `SQLITE_TRACE_CLOSE`, `sqlite3_trace_v2` public static let close = Self(rawValue: UInt32(SQLITE_TRACE_CLOSE)) - - public var debugDescription: String { - switch self { - case .statement: return "SQLITE_TRACE_STMT" - case .profile: return "SQLITE_TRACE_PROFILE" - case .row: return "SQLITE_TRACE_ROW" - case .close: return "SQLITE_TRACE_CLOSE" - default: return "TraceEventCode(rawValue: \(rawValue))" - } - } } /// Registers or clears a trace callback for the specified events on this connection. From 33774135529b697292947d74e0d725453a46ba45 Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 19:52:16 +0200 Subject: [PATCH 5/6] Add valid descriptions to OptionSet structures --- .../LSQLite/Database/Database+Function.swift | 57 ++++++++- Sources/LSQLite/Database/Database+Open.swift | 118 +++++++++++++++++- Sources/LSQLite/Database/Database+Trace.swift | 44 ++++++- .../LSQLite/Statement/Statement+Prepare.swift | 43 ++++++- 4 files changed, 258 insertions(+), 4 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Database/Database+Function.swift index 8f0bf99..9268b35 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Database/Database+Function.swift @@ -92,7 +92,7 @@ extension Database { /// Function flags describing determinism and security properties for user-defined SQL functions. /// /// Related SQLite: `sqlite3_create_function_v2`, `sqlite3_create_window_function`, `SQLITE_DETERMINISTIC`, `SQLITE_DIRECTONLY`, `SQLITE_SUBTYPE`, `SQLITE_INNOCUOUS`, `SQLITE_RESULT_SUBTYPE`, `SQLITE_SELFORDER1` - @frozen public struct FunctionFlag: Hashable, OptionSet { + @frozen public struct FunctionFlag: Hashable, OptionSet, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -103,26 +103,81 @@ extension Database { /// /// Related SQLite: `SQLITE_DETERMINISTIC` public static let deterministic = Self(rawValue: SQLITE_DETERMINISTIC) + /// Restricts the function to top-level SQL only. /// /// Related SQLite: `SQLITE_DIRECTONLY` public static let directOnly = Self(rawValue: SQLITE_DIRECTONLY) + /// Indicates the function inspects argument subtypes via `sqlite3_value_subtype`. /// /// Related SQLite: `SQLITE_SUBTYPE` public static let subtype = Self(rawValue: SQLITE_SUBTYPE) + /// Marks the function as innocuous for trusted schema checks. /// /// Related SQLite: `SQLITE_INNOCUOUS` public static let innocuous = Self(rawValue: SQLITE_INNOCUOUS) + /// Indicates the function may call `sqlite3_result_subtype`. /// /// Related SQLite: `SQLITE_RESULT_SUBTYPE` public static let resultSubtype = Self(rawValue: SQLITE_RESULT_SUBTYPE) + /// Declares an ordered-set aggregate that sorts its first argument. /// /// Related SQLite: `SQLITE_SELFORDER1` public static let selfOrder1 = Self(rawValue: SQLITE_SELFORDER1) + + private static let knownMask: UInt32 = UInt32(bitPattern: Self.deterministic.rawValue) + | UInt32(bitPattern: Self.directOnly.rawValue) + | UInt32(bitPattern: Self.subtype.rawValue) + | UInt32(bitPattern: Self.innocuous.rawValue) + | UInt32(bitPattern: Self.resultSubtype.rawValue) + | UInt32(bitPattern: Self.selfOrder1.rawValue) + + private static func hexString(_ rawValue: UInt32) -> String { + "0x" + String(rawValue, radix: 16, uppercase: true) + } + + public var description: String { + var parts: [String] = [] + if contains(.deterministic) { parts.append(".deterministic") } + if contains(.directOnly) { parts.append(".directOnly") } + if contains(.subtype) { parts.append(".subtype") } + if contains(.innocuous) { parts.append(".innocuous") } + if contains(.resultSubtype) { parts.append(".resultSubtype") } + if contains(.selfOrder1) { parts.append(".selfOrder1") } + + let rawBits = UInt32(bitPattern: rawValue) + let unknownBits = rawBits & ~Self.knownMask + if unknownBits != 0 { + if parts.isEmpty { return "unknown" } + parts.append("unknown") + } + if parts.isEmpty { return "[]" } + return "[\(parts.joined(separator: ", "))]" + } + + public var debugDescription: String { + var parts: [String] = [] + if contains(.deterministic) { parts.append("SQLITE_DETERMINISTIC") } + if contains(.directOnly) { parts.append("SQLITE_DIRECTONLY") } + if contains(.subtype) { parts.append("SQLITE_SUBTYPE") } + if contains(.innocuous) { parts.append("SQLITE_INNOCUOUS") } + if contains(.resultSubtype) { parts.append("SQLITE_RESULT_SUBTYPE") } + if contains(.selfOrder1) { parts.append("SQLITE_SELFORDER1") } + + let rawBits = UInt32(bitPattern: rawValue) + let unknownBits = rawBits & ~Self.knownMask + if unknownBits != 0 { + let hexValue = Self.hexString(rawBits) + if parts.isEmpty { return hexValue } + parts.append(hexValue) + } + if parts.isEmpty { return "[]" } + return parts.joined(separator: "|") + } } /// Registers or redefines a scalar or aggregate SQL function on this connection. diff --git a/Sources/LSQLite/Database/Database+Open.swift b/Sources/LSQLite/Database/Database+Open.swift index 7033e63..4fc80cf 100644 --- a/Sources/LSQLite/Database/Database+Open.swift +++ b/Sources/LSQLite/Database/Database+Open.swift @@ -34,7 +34,7 @@ extension Database { /// Flags passed to `open(_:at:withOpenFlags:)` and custom VFS xOpen calls. /// /// Related SQLite: `sqlite3_open_v2`, `sqlite3_vfs.xOpen`, `SQLITE_OPEN_*` - @frozen public struct OpenFlag: OptionSet { + @frozen public struct OpenFlag: OptionSet, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: Int32 @inlinable public init(rawValue: Int32) { @@ -68,6 +68,122 @@ extension Database { public static let fileProtectionNone = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_NONE) public static let fileProtectionMask = Self(rawValue: SQLITE_OPEN_FILEPROTECTION_MASK) #endif + + private static let knownMask: UInt32 = { + var mask = UInt32(bitPattern: Self.readonly.rawValue) + mask |= UInt32(bitPattern: Self.readwrite.rawValue) + mask |= UInt32(bitPattern: Self.create.rawValue) + mask |= UInt32(bitPattern: Self.deleteOnClose.rawValue) + mask |= UInt32(bitPattern: Self.exclusive.rawValue) + mask |= UInt32(bitPattern: Self.autoproxy.rawValue) + mask |= UInt32(bitPattern: Self.uri.rawValue) + mask |= UInt32(bitPattern: Self.memory.rawValue) + mask |= UInt32(bitPattern: Self.mainDB.rawValue) + mask |= UInt32(bitPattern: Self.tempDB.rawValue) + mask |= UInt32(bitPattern: Self.transientDB.rawValue) + mask |= UInt32(bitPattern: Self.mainJournal.rawValue) + mask |= UInt32(bitPattern: Self.tempJournal.rawValue) + mask |= UInt32(bitPattern: Self.subjournal.rawValue) + mask |= UInt32(bitPattern: Self.masterJournal.rawValue) + mask |= UInt32(bitPattern: Self.noMutex.rawValue) + mask |= UInt32(bitPattern: Self.fullMutex.rawValue) + mask |= UInt32(bitPattern: Self.sharedCache.rawValue) + mask |= UInt32(bitPattern: Self.privateCache.rawValue) + mask |= UInt32(bitPattern: Self.wal.rawValue) +#if canImport(Darwin) + mask |= UInt32(bitPattern: Self.fileProtectionComplete.rawValue) + mask |= UInt32(bitPattern: Self.fileProtectionCompleteUnlessOpen.rawValue) + mask |= UInt32(bitPattern: Self.fileProtectionCompleteUntilFirstUserAuthentication.rawValue) + mask |= UInt32(bitPattern: Self.fileProtectionNone.rawValue) + mask |= UInt32(bitPattern: Self.fileProtectionMask.rawValue) +#endif + return mask + }() + + private static func hexString(_ rawValue: UInt32) -> String { + "0x" + String(rawValue, radix: 16, uppercase: true) + } + + public var description: String { + var parts: [String] = [] + if contains(.readonly) { parts.append(".readonly") } + if contains(.readwrite) { parts.append(".readwrite") } + if contains(.create) { parts.append(".create") } + if contains(.deleteOnClose) { parts.append(".deleteOnClose") } + if contains(.exclusive) { parts.append(".exclusive") } + if contains(.autoproxy) { parts.append(".autoproxy") } + if contains(.uri) { parts.append(".uri") } + if contains(.memory) { parts.append(".memory") } + if contains(.mainDB) { parts.append(".mainDB") } + if contains(.tempDB) { parts.append(".tempDB") } + if contains(.transientDB) { parts.append(".transientDB") } + if contains(.mainJournal) { parts.append(".mainJournal") } + if contains(.tempJournal) { parts.append(".tempJournal") } + if contains(.subjournal) { parts.append(".subjournal") } + if contains(.masterJournal) { parts.append(".masterJournal") } + if contains(.noMutex) { parts.append(".noMutex") } + if contains(.fullMutex) { parts.append(".fullMutex") } + if contains(.sharedCache) { parts.append(".sharedCache") } + if contains(.privateCache) { parts.append(".privateCache") } + if contains(.wal) { parts.append(".wal") } +#if canImport(Darwin) + if contains(.fileProtectionComplete) { parts.append(".fileProtectionComplete") } + if contains(.fileProtectionCompleteUnlessOpen) { parts.append(".fileProtectionCompleteUnlessOpen") } + if contains(.fileProtectionCompleteUntilFirstUserAuthentication) { parts.append(".fileProtectionCompleteUntilFirstUserAuthentication") } + if contains(.fileProtectionNone) { parts.append(".fileProtectionNone") } + if contains(.fileProtectionMask) { parts.append(".fileProtectionMask") } +#endif + + let rawBits = UInt32(bitPattern: rawValue) + let unknownBits = rawBits & ~Self.knownMask + if unknownBits != 0 { + if parts.isEmpty { return "unknown" } + parts.append("unknown") + } + if parts.isEmpty { return "[]" } + return "[\(parts.joined(separator: ", "))]" + } + + public var debugDescription: String { + var parts: [String] = [] + if contains(.readonly) { parts.append("SQLITE_OPEN_READONLY") } + if contains(.readwrite) { parts.append("SQLITE_OPEN_READWRITE") } + if contains(.create) { parts.append("SQLITE_OPEN_CREATE") } + if contains(.deleteOnClose) { parts.append("SQLITE_OPEN_DELETEONCLOSE") } + if contains(.exclusive) { parts.append("SQLITE_OPEN_EXCLUSIVE") } + if contains(.autoproxy) { parts.append("SQLITE_OPEN_AUTOPROXY") } + if contains(.uri) { parts.append("SQLITE_OPEN_URI") } + if contains(.memory) { parts.append("SQLITE_OPEN_MEMORY") } + if contains(.mainDB) { parts.append("SQLITE_OPEN_MAIN_DB") } + if contains(.tempDB) { parts.append("SQLITE_OPEN_TEMP_DB") } + if contains(.transientDB) { parts.append("SQLITE_OPEN_TRANSIENT_DB") } + if contains(.mainJournal) { parts.append("SQLITE_OPEN_MAIN_JOURNAL") } + if contains(.tempJournal) { parts.append("SQLITE_OPEN_TEMP_JOURNAL") } + if contains(.subjournal) { parts.append("SQLITE_OPEN_SUBJOURNAL") } + if contains(.masterJournal) { parts.append("SQLITE_OPEN_MASTER_JOURNAL") } + if contains(.noMutex) { parts.append("SQLITE_OPEN_NOMUTEX") } + if contains(.fullMutex) { parts.append("SQLITE_OPEN_FULLMUTEX") } + if contains(.sharedCache) { parts.append("SQLITE_OPEN_SHAREDCACHE") } + if contains(.privateCache) { parts.append("SQLITE_OPEN_PRIVATECACHE") } + if contains(.wal) { parts.append("SQLITE_OPEN_WAL") } +#if canImport(Darwin) + if contains(.fileProtectionComplete) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETE") } + if contains(.fileProtectionCompleteUnlessOpen) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETEUNLESSOPEN") } + if contains(.fileProtectionCompleteUntilFirstUserAuthentication) { parts.append("SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION") } + if contains(.fileProtectionNone) { parts.append("SQLITE_OPEN_FILEPROTECTION_NONE") } + if contains(.fileProtectionMask) { parts.append("SQLITE_OPEN_FILEPROTECTION_MASK") } +#endif + + let rawBits = UInt32(bitPattern: rawValue) + let unknownBits = rawBits & ~Self.knownMask + if unknownBits != 0 { + let hexValue = Self.hexString(rawBits) + if parts.isEmpty { return hexValue } + parts.append(hexValue) + } + if parts.isEmpty { return "[]" } + return parts.joined(separator: "|") + } } /// Opens a database connection at the given filename using the supplied flags. diff --git a/Sources/LSQLite/Database/Database+Trace.swift b/Sources/LSQLite/Database/Database+Trace.swift index c5e67ec..6f52725 100644 --- a/Sources/LSQLite/Database/Database+Trace.swift +++ b/Sources/LSQLite/Database/Database+Trace.swift @@ -34,7 +34,7 @@ extension Database { /// Event mask values used when registering `setTraceCallback(for:userData:callback:)`. /// /// Related SQLite: `sqlite3_trace_v2`, `SQLITE_TRACE_STMT`, `SQLITE_TRACE_PROFILE`, `SQLITE_TRACE_ROW`, `SQLITE_TRACE_CLOSE` - @frozen public struct TraceEventCode: Hashable, OptionSet { + @frozen public struct TraceEventCode: Hashable, OptionSet, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: UInt32 @inlinable public init(rawValue: UInt32) { @@ -60,6 +60,48 @@ extension Database { /// /// Related SQLite: `SQLITE_TRACE_CLOSE`, `sqlite3_trace_v2` public static let close = Self(rawValue: UInt32(SQLITE_TRACE_CLOSE)) + + private static let knownMask: UInt32 = Self.statement.rawValue + | Self.profile.rawValue + | Self.row.rawValue + | Self.close.rawValue + + private static func hexString(_ rawValue: UInt32) -> String { + "0x" + String(rawValue, radix: 16, uppercase: true) + } + + public var description: String { + var parts: [String] = [] + if contains(.statement) { parts.append(".statement") } + if contains(.profile) { parts.append(".profile") } + if contains(.row) { parts.append(".row") } + if contains(.close) { parts.append(".close") } + + let unknownBits = rawValue & ~Self.knownMask + if unknownBits != 0 { + if parts.isEmpty { return "unknown" } + parts.append("unknown") + } + if parts.isEmpty { return "[]" } + return "[\(parts.joined(separator: ", "))]" + } + + public var debugDescription: String { + var parts: [String] = [] + if contains(.statement) { parts.append("SQLITE_TRACE_STMT") } + if contains(.profile) { parts.append("SQLITE_TRACE_PROFILE") } + if contains(.row) { parts.append("SQLITE_TRACE_ROW") } + if contains(.close) { parts.append("SQLITE_TRACE_CLOSE") } + + let unknownBits = rawValue & ~Self.knownMask + if unknownBits != 0 { + let hexValue = Self.hexString(rawValue) + if parts.isEmpty { return hexValue } + parts.append(hexValue) + } + if parts.isEmpty { return "[]" } + return parts.joined(separator: "|") + } } /// Registers or clears a trace callback for the specified events on this connection. diff --git a/Sources/LSQLite/Statement/Statement+Prepare.swift b/Sources/LSQLite/Statement/Statement+Prepare.swift index 16e8d3c..296a6ed 100644 --- a/Sources/LSQLite/Statement/Statement+Prepare.swift +++ b/Sources/LSQLite/Statement/Statement+Prepare.swift @@ -4,7 +4,7 @@ extension Statement { /// Flags for sqlite3_prepare_v3 prepFlags argument. /// /// Related SQLite: `sqlite3_prepare_v3`, `SQLITE_PREPARE_PERSISTENT`, `SQLITE_PREPARE_NORMALIZE`, `SQLITE_PREPARE_NO_VTAB` - @frozen public struct PrepareFlag: OptionSet { + @frozen public struct PrepareFlag: OptionSet, CustomStringConvertible, CustomDebugStringConvertible { public let rawValue: UInt32 @inlinable public init(rawValue: UInt32) { @@ -15,14 +15,55 @@ extension Statement { /// /// Related SQLite: `SQLITE_PREPARE_PERSISTENT` public static let persistent = Self(rawValue: UInt32(SQLITE_PREPARE_PERSISTENT)) + /// Normalization flag (currently a no-op). /// /// Related SQLite: `SQLITE_PREPARE_NORMALIZE` public static let normalize = Self(rawValue: UInt32(SQLITE_PREPARE_NORMALIZE)) + /// Causes preparation to fail if the SQL uses virtual tables. /// /// Related SQLite: `SQLITE_PREPARE_NO_VTAB` public static let noVTab = Self(rawValue: UInt32(SQLITE_PREPARE_NO_VTAB)) + + private static let knownMask: UInt32 = Self.persistent.rawValue + | Self.normalize.rawValue + | Self.noVTab.rawValue + + private static func hexString(_ rawValue: UInt32) -> String { + "0x" + String(rawValue, radix: 16, uppercase: true) + } + + public var description: String { + var parts: [String] = [] + if contains(.persistent) { parts.append(".persistent") } + if contains(.normalize) { parts.append(".normalize") } + if contains(.noVTab) { parts.append(".noVTab") } + + let unknownBits = rawValue & ~Self.knownMask + if unknownBits != 0 { + if parts.isEmpty { return "unknown" } + parts.append("unknown") + } + if parts.isEmpty { return "[]" } + return "[\(parts.joined(separator: ", "))]" + } + + public var debugDescription: String { + var parts: [String] = [] + if contains(.persistent) { parts.append("SQLITE_PREPARE_PERSISTENT") } + if contains(.normalize) { parts.append("SQLITE_PREPARE_NORMALIZE") } + if contains(.noVTab) { parts.append("SQLITE_PREPARE_NO_VTAB") } + + let unknownBits = rawValue & ~Self.knownMask + if unknownBits != 0 { + let hexValue = Self.hexString(rawValue) + if parts.isEmpty { return hexValue } + parts.append(hexValue) + } + if parts.isEmpty { return "[]" } + return parts.joined(separator: "|") + } } /// Compiles UTF-8 SQL into a prepared statement using sqlite3_prepare_v2. From b1cc80b6fe10f59652c7bc24744e59fa02b0372d Mon Sep 17 00:00:00 2001 From: Anton Sergeev Date: Sat, 27 Dec 2025 20:20:58 +0200 Subject: [PATCH 6/6] Remove relatively new SQLITE_SELFORDER1 --- .../LSQLite/Database/Database+Function.swift | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Sources/LSQLite/Database/Database+Function.swift b/Sources/LSQLite/Database/Database+Function.swift index 9268b35..b7d4d31 100644 --- a/Sources/LSQLite/Database/Database+Function.swift +++ b/Sources/LSQLite/Database/Database+Function.swift @@ -124,17 +124,14 @@ extension Database { /// Related SQLite: `SQLITE_RESULT_SUBTYPE` public static let resultSubtype = Self(rawValue: SQLITE_RESULT_SUBTYPE) - /// Declares an ordered-set aggregate that sorts its first argument. - /// - /// Related SQLite: `SQLITE_SELFORDER1` - public static let selfOrder1 = Self(rawValue: SQLITE_SELFORDER1) - - private static let knownMask: UInt32 = UInt32(bitPattern: Self.deterministic.rawValue) - | UInt32(bitPattern: Self.directOnly.rawValue) - | UInt32(bitPattern: Self.subtype.rawValue) - | UInt32(bitPattern: Self.innocuous.rawValue) - | UInt32(bitPattern: Self.resultSubtype.rawValue) - | UInt32(bitPattern: Self.selfOrder1.rawValue) + private static let knownMask: UInt32 = { + var mask = UInt32(bitPattern: Self.deterministic.rawValue) + mask |= UInt32(bitPattern: Self.directOnly.rawValue) + mask |= UInt32(bitPattern: Self.subtype.rawValue) + mask |= UInt32(bitPattern: Self.innocuous.rawValue) + mask |= UInt32(bitPattern: Self.resultSubtype.rawValue) + return mask + }() private static func hexString(_ rawValue: UInt32) -> String { "0x" + String(rawValue, radix: 16, uppercase: true) @@ -147,7 +144,6 @@ extension Database { if contains(.subtype) { parts.append(".subtype") } if contains(.innocuous) { parts.append(".innocuous") } if contains(.resultSubtype) { parts.append(".resultSubtype") } - if contains(.selfOrder1) { parts.append(".selfOrder1") } let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask @@ -166,7 +162,6 @@ extension Database { if contains(.subtype) { parts.append("SQLITE_SUBTYPE") } if contains(.innocuous) { parts.append("SQLITE_INNOCUOUS") } if contains(.resultSubtype) { parts.append("SQLITE_RESULT_SUBTYPE") } - if contains(.selfOrder1) { parts.append("SQLITE_SELFORDER1") } let rawBits = UInt32(bitPattern: rawValue) let unknownBits = rawBits & ~Self.knownMask