Add unit tests
This commit is contained in:
@@ -34,10 +34,8 @@ let package = Package(
|
|||||||
.testTarget(
|
.testTarget(
|
||||||
name: "DataLiteCoreTests",
|
name: "DataLiteCoreTests",
|
||||||
dependencies: ["DataLiteCore"],
|
dependencies: ["DataLiteCore"],
|
||||||
resources: [
|
cSettings: [
|
||||||
.copy("Resources/valid_script.sql"),
|
.define("SQLITE_HAS_CODEC")
|
||||||
.copy("Resources/empty_script.sql"),
|
|
||||||
.copy("Resources/invalid_script.sql")
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extension Connection {
|
|||||||
/// - ``file(path:)``: A database at a specific file path or URI (persistent).
|
/// - ``file(path:)``: A database at a specific file path or URI (persistent).
|
||||||
/// - ``inMemory``: An in-memory database that exists only in RAM.
|
/// - ``inMemory``: An in-memory database that exists only in RAM.
|
||||||
/// - ``temporary``: A temporary on-disk database deleted when the connection closes.
|
/// - ``temporary``: A temporary on-disk database deleted when the connection closes.
|
||||||
public enum Location {
|
public enum Location: Sendable {
|
||||||
/// A database stored at a given file path or URI.
|
/// A database stored at a given file path or URI.
|
||||||
///
|
///
|
||||||
/// Use this for persistent databases located on disk or referenced via SQLite URI.
|
/// Use this for persistent databases located on disk or referenced via SQLite URI.
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ extension Connection: ConnectionProtocol {
|
|||||||
sqlite3_key(connection, key.keyValue, key.length)
|
sqlite3_key(connection, key.keyValue, key.length)
|
||||||
}
|
}
|
||||||
guard status == SQLITE_OK else {
|
guard status == SQLITE_OK else {
|
||||||
throw SQLiteError(connection)
|
throw SQLiteError(code: status, message: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +163,15 @@ extension Connection: ConnectionProtocol {
|
|||||||
sqlite3_rekey(connection, key.keyValue, key.length)
|
sqlite3_rekey(connection, key.keyValue, key.length)
|
||||||
}
|
}
|
||||||
guard status == SQLITE_OK else {
|
guard status == SQLITE_OK else {
|
||||||
throw SQLiteError(connection)
|
throw SQLiteError(code: status, message: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(delegate: any ConnectionDelegate) {
|
public func add(delegate: any ConnectionDelegate) {
|
||||||
delegates.append(.init(delegate: delegate))
|
if !delegates.contains(where: { $0.delegate === delegate }) {
|
||||||
delegates.removeAll { $0.delegate == nil }
|
delegates.append(.init(delegate: delegate))
|
||||||
|
delegates.removeAll { $0.delegate == nil }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(delegate: any ConnectionDelegate) {
|
public func remove(delegate: any ConnectionDelegate) {
|
||||||
@@ -179,8 +181,10 @@ extension Connection: ConnectionProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func add(trace delegate: any ConnectionTraceDelegate) {
|
public func add(trace delegate: any ConnectionTraceDelegate) {
|
||||||
traceDelegates.append(.init(delegate: delegate))
|
if !traceDelegates.contains(where: { $0.delegate === delegate }) {
|
||||||
traceDelegates.removeAll { $0.delegate == nil }
|
traceDelegates.append(.init(delegate: delegate))
|
||||||
|
traceDelegates.removeAll { $0.delegate == nil }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(trace delegate: any ConnectionTraceDelegate) {
|
public func remove(trace delegate: any ConnectionTraceDelegate) {
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ public final class Statement {
|
|||||||
// MARK: - StatementProtocol
|
// MARK: - StatementProtocol
|
||||||
|
|
||||||
extension Statement: StatementProtocol {
|
extension Statement: StatementProtocol {
|
||||||
|
public var sql: String? {
|
||||||
|
let cSQL = sqlite3_sql(statement)
|
||||||
|
guard let cSQL else { return nil }
|
||||||
|
return String(cString: cSQL)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var expandedSQL: String? {
|
||||||
|
let cSQL = sqlite3_expanded_sql(statement)
|
||||||
|
defer { sqlite3_free(cSQL) }
|
||||||
|
guard let cSQL else { return nil }
|
||||||
|
return String(cString: cSQL)
|
||||||
|
}
|
||||||
|
|
||||||
public func parameterCount() -> Int32 {
|
public func parameterCount() -> Int32 {
|
||||||
sqlite3_bind_parameter_count(statement)
|
sqlite3_bind_parameter_count(statement)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ public enum JournalMode: String, SQLiteRepresentable {
|
|||||||
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
||||||
public var rawValue: String {
|
public var rawValue: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .delete: "DELETE"
|
case .delete: "DELETE"
|
||||||
case .truncate: "TRUNCATE"
|
case .truncate: "TRUNCATE"
|
||||||
case .persist: "PERSIST"
|
case .persist: "PERSIST"
|
||||||
case .memory: "MEMORY"
|
case .memory: "MEMORY"
|
||||||
case .wal: "WAL"
|
case .wal: "WAL"
|
||||||
case .off: "OFF"
|
case .off: "OFF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ public enum JournalMode: String, SQLiteRepresentable {
|
|||||||
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
||||||
public init?(rawValue: String) {
|
public init?(rawValue: String) {
|
||||||
switch rawValue.uppercased() {
|
switch rawValue.uppercased() {
|
||||||
case "DELETE": self = .delete
|
case "DELETE": self = .delete
|
||||||
case "TRUNCATE": self = .truncate
|
case "TRUNCATE": self = .truncate
|
||||||
case "PERSIST": self = .persist
|
case "PERSIST": self = .persist
|
||||||
case "MEMORY": self = .memory
|
case "MEMORY": self = .memory
|
||||||
case "WAL": self = .wal
|
case "WAL": self = .wal
|
||||||
case "OFF": self = .off
|
case "OFF": self = .off
|
||||||
default: return nil
|
default: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,48 @@ public enum TransactionType: String, CustomStringConvertible {
|
|||||||
/// With `BEGIN DEFERRED`, no locks are acquired immediately. If the first statement is a read
|
/// With `BEGIN DEFERRED`, no locks are acquired immediately. If the first statement is a read
|
||||||
/// (`SELECT`), a read transaction begins. If it is a write statement, a write transaction
|
/// (`SELECT`), a read transaction begins. If it is a write statement, a write transaction
|
||||||
/// begins instead. Deferred transactions allow greater concurrency and are the default mode.
|
/// begins instead. Deferred transactions allow greater concurrency and are the default mode.
|
||||||
case deferred = "DEFERRED"
|
case deferred
|
||||||
|
|
||||||
/// Starts a write transaction immediately.
|
/// Starts a write transaction immediately.
|
||||||
///
|
///
|
||||||
/// With `BEGIN IMMEDIATE`, a reserved lock is acquired right away to ensure that no other
|
/// With `BEGIN IMMEDIATE`, a reserved lock is acquired right away to ensure that no other
|
||||||
/// connection can start a conflicting write. The statement may fail with `SQLITE_BUSY` if
|
/// connection can start a conflicting write. The statement may fail with `SQLITE_BUSY` if
|
||||||
/// another write transaction is already active.
|
/// another write transaction is already active.
|
||||||
case immediate = "IMMEDIATE"
|
case immediate
|
||||||
|
|
||||||
/// Starts an exclusive write transaction.
|
/// Starts an exclusive write transaction.
|
||||||
///
|
///
|
||||||
/// With `BEGIN EXCLUSIVE`, a write lock is acquired immediately. In rollback journal mode, it
|
/// With `BEGIN EXCLUSIVE`, a write lock is acquired immediately. In rollback journal mode, it
|
||||||
/// also prevents other connections from reading the database while the transaction is active.
|
/// also prevents other connections from reading the database while the transaction is active.
|
||||||
/// In WAL mode, it behaves the same as `.immediate`.
|
/// In WAL mode, it behaves the same as `.immediate`.
|
||||||
case exclusive = "EXCLUSIVE"
|
case exclusive
|
||||||
|
|
||||||
/// A textual representation of the transaction type.
|
/// A textual representation of the transaction type.
|
||||||
public var description: String {
|
public var description: String {
|
||||||
rawValue
|
rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the SQLite keyword that represents the transaction type.
|
||||||
|
///
|
||||||
|
/// The value is always uppercased to match the keywords used by SQLite statements.
|
||||||
|
public var rawValue: String {
|
||||||
|
switch self {
|
||||||
|
case .deferred: "DEFERRED"
|
||||||
|
case .immediate: "IMMEDIATE"
|
||||||
|
case .exclusive: "EXCLUSIVE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a transaction type from an SQLite keyword.
|
||||||
|
///
|
||||||
|
/// The initializer accepts any ASCII case variant of the keyword (`"deferred"`, `"Deferred"`,
|
||||||
|
/// etc.). Returns `nil` if the string does not correspond to a supported transaction type.
|
||||||
|
public init?(rawValue: String) {
|
||||||
|
switch rawValue.uppercased() {
|
||||||
|
case "DEFERRED": self = .deferred
|
||||||
|
case "IMMEDIATE": self = .immediate
|
||||||
|
case "EXCLUSIVE": self = .exclusive
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import Foundation
|
|||||||
|
|
||||||
/// A delegate that observes connection-level database events.
|
/// A delegate that observes connection-level database events.
|
||||||
///
|
///
|
||||||
/// Conforming types can monitor row-level updates and transaction lifecycle events. All methods are
|
/// Conforming types can monitor row-level updates and transaction lifecycle events. This protocol
|
||||||
/// optional — default implementations do nothing.
|
/// is typically used for debugging, logging, or synchronizing application state with database
|
||||||
///
|
/// changes.
|
||||||
/// This protocol is typically used for debugging, logging, or synchronizing application state with
|
|
||||||
/// database changes.
|
|
||||||
///
|
///
|
||||||
/// - Important: Delegate methods are invoked synchronously on SQLite’s internal execution thread.
|
/// - Important: Delegate methods are invoked synchronously on SQLite’s internal execution thread.
|
||||||
/// Implementations must be lightweight and non-blocking to avoid slowing down SQL operations.
|
/// Implementations must be lightweight and non-blocking to avoid slowing down SQL operations.
|
||||||
@@ -43,9 +41,3 @@ public protocol ConnectionDelegate: AnyObject {
|
|||||||
/// - Parameter connection: The connection that rolled back.
|
/// - Parameter connection: The connection that rolled back.
|
||||||
func connectionDidRollback(_ connection: ConnectionProtocol)
|
func connectionDidRollback(_ connection: ConnectionProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension ConnectionDelegate {
|
|
||||||
func connection(_ connection: ConnectionProtocol, didUpdate action: SQLiteAction) {}
|
|
||||||
func connectionWillCommit(_ connection: ConnectionProtocol) throws {}
|
|
||||||
func connectionDidRollback(_ connection: ConnectionProtocol) {}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import Foundation
|
|||||||
///
|
///
|
||||||
/// - ``isAutocommit``
|
/// - ``isAutocommit``
|
||||||
/// - ``isReadonly``
|
/// - ``isReadonly``
|
||||||
/// - ``busyTimeout``
|
|
||||||
///
|
///
|
||||||
/// ### Accessing PRAGMA Values
|
/// ### Accessing PRAGMA Values
|
||||||
///
|
///
|
||||||
|
/// - ``busyTimeout``
|
||||||
/// - ``applicationID``
|
/// - ``applicationID``
|
||||||
/// - ``foreignKeys``
|
/// - ``foreignKeys``
|
||||||
/// - ``journalMode``
|
/// - ``journalMode``
|
||||||
@@ -84,6 +84,8 @@ public protocol ConnectionProtocol: AnyObject {
|
|||||||
/// - SeeAlso: [Determine if a database is read-only](https://sqlite.org/c3ref/db_readonly.html)
|
/// - SeeAlso: [Determine if a database is read-only](https://sqlite.org/c3ref/db_readonly.html)
|
||||||
var isReadonly: Bool { get }
|
var isReadonly: Bool { get }
|
||||||
|
|
||||||
|
// MARK: - PRAGMA Accessors
|
||||||
|
|
||||||
/// The busy timeout of the connection, in milliseconds.
|
/// The busy timeout of the connection, in milliseconds.
|
||||||
///
|
///
|
||||||
/// Defines how long SQLite waits for a locked database to become available before returning
|
/// Defines how long SQLite waits for a locked database to become available before returning
|
||||||
@@ -93,8 +95,6 @@ public protocol ConnectionProtocol: AnyObject {
|
|||||||
/// - SeeAlso: [Set A Busy Timeout](https://sqlite.org/c3ref/busy_timeout.html)
|
/// - SeeAlso: [Set A Busy Timeout](https://sqlite.org/c3ref/busy_timeout.html)
|
||||||
var busyTimeout: Int32 { get set }
|
var busyTimeout: Int32 { get set }
|
||||||
|
|
||||||
// MARK: - PRAGMA Accessors
|
|
||||||
|
|
||||||
/// The application identifier stored in the database header.
|
/// The application identifier stored in the database header.
|
||||||
///
|
///
|
||||||
/// Used to distinguish database files created by different applications or file formats. This
|
/// Used to distinguish database files created by different applications or file formats. This
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import Foundation
|
|||||||
///
|
///
|
||||||
/// ## Topics
|
/// ## Topics
|
||||||
///
|
///
|
||||||
|
/// ### Retrieving Statement SQL
|
||||||
|
///
|
||||||
|
/// - ``sql``
|
||||||
|
/// - ``expandedSQL``
|
||||||
|
///
|
||||||
/// ### Binding Parameters
|
/// ### Binding Parameters
|
||||||
///
|
///
|
||||||
/// - ``parameterCount()``
|
/// - ``parameterCount()``
|
||||||
@@ -34,6 +39,20 @@ import Foundation
|
|||||||
/// - ``columnValue(at:)->T?``
|
/// - ``columnValue(at:)->T?``
|
||||||
/// - ``currentRow()``
|
/// - ``currentRow()``
|
||||||
public protocol StatementProtocol {
|
public protocol StatementProtocol {
|
||||||
|
// MARK: - Retrieving Statement SQL
|
||||||
|
|
||||||
|
/// The original SQL text used to create this prepared statement.
|
||||||
|
///
|
||||||
|
/// Returns the statement text as it was supplied when the statement was prepared. Useful for
|
||||||
|
/// diagnostics or query introspection.
|
||||||
|
var sql: String? { get }
|
||||||
|
|
||||||
|
/// The SQL text with all parameter values expanded into their literal forms.
|
||||||
|
///
|
||||||
|
/// Shows the actual SQL string that would be executed after parameter binding. Useful for
|
||||||
|
/// debugging and logging queries.
|
||||||
|
var expandedSQL: String? { get }
|
||||||
|
|
||||||
// MARK: - Binding Parameters
|
// MARK: - Binding Parameters
|
||||||
|
|
||||||
/// Returns the number of parameters in the prepared SQLite statement.
|
/// Returns the number of parameters in the prepared SQLite statement.
|
||||||
@@ -101,7 +120,7 @@ public protocol StatementProtocol {
|
|||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: The ``SQLiteValue`` to bind.
|
/// - value: The ``SQLiteValue`` to bind.
|
||||||
/// - name: The parameter name as written in SQL, including its prefix.
|
/// - name: The parameter name exactly as written in SQL, including its prefix.
|
||||||
/// - Throws: ``SQLiteError`` if binding fails.
|
/// - Throws: ``SQLiteError`` if binding fails.
|
||||||
///
|
///
|
||||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||||
@@ -130,7 +149,7 @@ public protocol StatementProtocol {
|
|||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: The value to bind. If `nil`, `NULL` is bound.
|
/// - value: The value to bind. If `nil`, `NULL` is bound.
|
||||||
/// - name: The parameter name as written in SQL, including its prefix.
|
/// - name: The parameter name exactly as written in SQL, including its prefix.
|
||||||
/// - Throws: ``SQLiteError`` if binding fails.
|
/// - Throws: ``SQLiteError`` if binding fails.
|
||||||
///
|
///
|
||||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public struct SQLiteError: Error, Equatable, CustomStringConvertible, Sendable {
|
|||||||
/// name (`SQLiteError`), the numeric code, and the corresponding message, making it useful for
|
/// name (`SQLiteError`), the numeric code, and the corresponding message, making it useful for
|
||||||
/// debugging, logging, or diagnostic displays.
|
/// debugging, logging, or diagnostic displays.
|
||||||
public var description: String {
|
public var description: String {
|
||||||
"\(Self.self) code: \(code) message: \(message)"
|
"\(Self.self)(\(code)): \(message)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new error instance with the specified result code and message.
|
/// Creates a new error instance with the specified result code and message.
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct ConnectionKeyTests {
|
struct ConnectionKeyTests {
|
||||||
@Test func testPassphrase() {
|
@Test func passphrase() {
|
||||||
let key = Connection.Key.passphrase("secret123")
|
let key = Connection.Key.passphrase("secret123")
|
||||||
#expect(key.keyValue == "secret123")
|
#expect(key.keyValue == "secret123")
|
||||||
#expect(key.length == 9)
|
#expect(key.length == 9)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testRawKey() {
|
@Test func rawKey() {
|
||||||
let keyData = Data([0x01, 0xAB, 0xCD, 0xEF])
|
let keyData = Data([0x01, 0xAB, 0xCD, 0xEF])
|
||||||
let key = Connection.Key.rawKey(keyData)
|
let key = Connection.Key.rawKey(keyData)
|
||||||
#expect(key.keyValue == "X'01ABCDEF'")
|
#expect(key.keyValue == "X'01ABCDEF'")
|
||||||
#expect(key.length == 11)
|
#expect(key.length == 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testRawKeyLengthConsistency() {
|
@Test func rawKeyLengthConsistency() {
|
||||||
let rawBytes = Data(repeating: 0x00, count: 32)
|
let rawBytes = Data(repeating: 0x00, count: 32)
|
||||||
let key = Connection.Key.rawKey(rawBytes)
|
let key = Connection.Key.rawKey(rawBytes)
|
||||||
let hexPart = key.keyValue.dropFirst(2).dropLast()
|
let hexPart = key.keyValue.dropFirst(2).dropLast()
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import Testing
|
|||||||
@testable import DataLiteCore
|
@testable import DataLiteCore
|
||||||
|
|
||||||
struct ConnectionLocationTests {
|
struct ConnectionLocationTests {
|
||||||
@Test func testFileLocationPath() {
|
@Test func fileLocationPath() {
|
||||||
let filePath = "/path/to/database.db"
|
let filePath = "/path/to/database.db"
|
||||||
let location = Connection.Location.file(path: filePath)
|
let location = Connection.Location.file(path: filePath)
|
||||||
#expect(location.path == filePath)
|
#expect(location.path == filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInMemoryLocationPath() {
|
@Test func inMemoryLocationPath() {
|
||||||
let inMemoryLocation = Connection.Location.inMemory
|
let inMemoryLocation = Connection.Location.inMemory
|
||||||
#expect(inMemoryLocation.path == ":memory:")
|
#expect(inMemoryLocation.path == ":memory:")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testTemporaryLocationPath() {
|
@Test func temporaryLocationPath() {
|
||||||
let temporaryLocation = Connection.Location.temporary
|
let temporaryLocation = Connection.Location.temporary
|
||||||
#expect(temporaryLocation.path == "")
|
#expect(temporaryLocation.path == "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,34 +3,34 @@ import DataLiteC
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct ConnectionOptionsTests {
|
struct ConnectionOptionsTests {
|
||||||
@Test func testReadOnlyOption() {
|
@Test func readOnlyOption() {
|
||||||
let options: Connection.Options = [.readonly]
|
let options: Connection.Options = [.readonly]
|
||||||
#expect(options.contains(.readonly))
|
#expect(options.contains(.readonly))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testReadWriteOption() {
|
@Test func readWriteOption() {
|
||||||
let options: Connection.Options = [.readwrite]
|
let options: Connection.Options = [.readwrite]
|
||||||
#expect(options.contains(.readwrite))
|
#expect(options.contains(.readwrite))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testCreateOption() {
|
@Test func createOption() {
|
||||||
let options: Connection.Options = [.create]
|
let options: Connection.Options = [.create]
|
||||||
#expect(options.contains(.create))
|
#expect(options.contains(.create))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testMultipleOptions() {
|
@Test func multipleOptions() {
|
||||||
let options: Connection.Options = [.readwrite, .create, .memory]
|
let options: Connection.Options = [.readwrite, .create, .memory]
|
||||||
#expect(options.contains(.readwrite))
|
#expect(options.contains(.readwrite))
|
||||||
#expect(options.contains(.create))
|
#expect(options.contains(.create))
|
||||||
#expect(options.contains(.memory))
|
#expect(options.contains(.memory))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testNoFollowOption() {
|
@Test func noFollowOption() {
|
||||||
let options: Connection.Options = [.nofollow]
|
let options: Connection.Options = [.nofollow]
|
||||||
#expect(options.contains(.nofollow))
|
#expect(options.contains(.nofollow))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testAllOptions() {
|
@Test func allOptions() {
|
||||||
let options: Connection.Options = [
|
let options: Connection.Options = [
|
||||||
.readonly, .readwrite, .create, .uri, .memory,
|
.readonly, .readwrite, .create, .uri, .memory,
|
||||||
.nomutex, .fullmutex, .sharedcache,
|
.nomutex, .fullmutex, .sharedcache,
|
||||||
@@ -50,7 +50,7 @@ struct ConnectionOptionsTests {
|
|||||||
#expect(options.contains(.nofollow))
|
#expect(options.contains(.nofollow))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testOptionsRawValue() {
|
@Test func optionsRawValue() {
|
||||||
let options: Connection.Options = [.readwrite, .create]
|
let options: Connection.Options = [.readwrite, .create]
|
||||||
let expectedRawValue = Int32(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
|
let expectedRawValue = Int32(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
|
||||||
#expect(options.rawValue == expectedRawValue)
|
#expect(options.rawValue == expectedRawValue)
|
||||||
|
|||||||
@@ -1,73 +1,71 @@
|
|||||||
import Foundation
|
|
||||||
import Testing
|
import Testing
|
||||||
|
import Foundation
|
||||||
import DataLiteC
|
import DataLiteC
|
||||||
import DataLiteCore
|
|
||||||
|
@testable import DataLiteCore
|
||||||
|
|
||||||
struct ConnectionTests {
|
struct ConnectionTests {
|
||||||
@Test func testIsAutocommitInitially() throws {
|
@Test(arguments: [
|
||||||
let connection = try Connection(
|
Connection.Location.inMemory,
|
||||||
location: .inMemory,
|
Connection.Location.temporary
|
||||||
options: [.create, .readwrite]
|
])
|
||||||
)
|
func initLocation(_ location: Connection.Location) throws {
|
||||||
#expect(connection.isAutocommit == true)
|
let _ = try Connection(location: location, options: [.create, .readwrite])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testIsAutocommitDuringTransaction() throws {
|
@Test func initPath() throws {
|
||||||
let connection = try Connection(
|
let dir = FileManager.default.temporaryDirectory
|
||||||
location: .inMemory,
|
let file = UUID().uuidString
|
||||||
options: [.create, .readwrite]
|
let path = dir.appending(component: file).path
|
||||||
)
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
try connection.beginTransaction()
|
let _ = try Connection(path: path, options: [.create, .readwrite])
|
||||||
#expect(connection.isAutocommit == false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testIsAutocommitAfterCommit() throws {
|
@Test func initPathFail() {
|
||||||
let connection = try Connection(
|
#expect(
|
||||||
location: .inMemory,
|
throws: SQLiteError(
|
||||||
options: [.create, .readwrite]
|
code: SQLITE_CANTOPEN,
|
||||||
|
message: "unable to open database file"
|
||||||
|
),
|
||||||
|
performing: {
|
||||||
|
try Connection(
|
||||||
|
path: "/invalid-path/",
|
||||||
|
options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
try connection.beginTransaction()
|
|
||||||
try connection.commitTransaction()
|
|
||||||
#expect(connection.isAutocommit == true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testIsAutocommitAfterRollback() throws {
|
@Test func isAutocommit() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory,
|
||||||
options: [.create, .readwrite]
|
options: [.create, .readwrite]
|
||||||
)
|
)
|
||||||
try connection.beginTransaction()
|
#expect(connection.isAutocommit)
|
||||||
try connection.rollbackTransaction()
|
|
||||||
#expect(connection.isAutocommit == true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(arguments: [
|
@Test(arguments: [
|
||||||
(Connection.Options.readwrite, false),
|
(Connection.Options.readwrite, false),
|
||||||
(Connection.Options.readonly, true)
|
(Connection.Options.readonly, true)
|
||||||
])
|
])
|
||||||
func testIsReadonly(
|
func isReadonly(
|
||||||
_ opt: Connection.Options,
|
_ options: Connection.Options,
|
||||||
_ isReadonly: Bool
|
_ expected: Bool
|
||||||
) throws {
|
) throws {
|
||||||
let url = FileManager.default.temporaryDirectory
|
let dir = FileManager.default.temporaryDirectory
|
||||||
.appendingPathComponent(UUID().uuidString)
|
let file = UUID().uuidString
|
||||||
.appendingPathExtension("sqlite")
|
let path = dir.appending(component: file).path
|
||||||
defer { try? FileManager.default.removeItem(at: url) }
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
let _ = try Connection(
|
|
||||||
location: .file(path: url.path),
|
let _ = try Connection(path: path, options: [.create, .readwrite])
|
||||||
options: [.create, .readwrite]
|
let connection = try Connection(path: path, options: options)
|
||||||
)
|
|
||||||
let connection = try Connection(
|
#expect(connection.isReadonly == expected)
|
||||||
location: .file(path: url.path),
|
|
||||||
options: [opt]
|
|
||||||
)
|
|
||||||
#expect(connection.isReadonly == isReadonly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testBusyTimeout() throws {
|
@Test func testBusyTimeout() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
)
|
||||||
connection.busyTimeout = 5000
|
connection.busyTimeout = 5000
|
||||||
#expect(try connection.get(pragma: .busyTimeout) == 5000)
|
#expect(try connection.get(pragma: .busyTimeout) == 5000)
|
||||||
@@ -76,50 +74,9 @@ struct ConnectionTests {
|
|||||||
#expect(connection.busyTimeout == 1000)
|
#expect(connection.busyTimeout == 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testBusyTimeoutSQLiteBusy() throws {
|
|
||||||
let url = FileManager.default.temporaryDirectory
|
|
||||||
.appendingPathComponent(UUID().uuidString)
|
|
||||||
.appendingPathExtension("sqlite")
|
|
||||||
defer { try? FileManager.default.removeItem(at: url) }
|
|
||||||
|
|
||||||
let oneConn = try Connection(
|
|
||||||
location: .file(path: url.path),
|
|
||||||
options: [.create, .readwrite, .fullmutex]
|
|
||||||
)
|
|
||||||
let twoConn = try Connection(
|
|
||||||
location: .file(path: url.path),
|
|
||||||
options: [.create, .readwrite, .fullmutex]
|
|
||||||
)
|
|
||||||
|
|
||||||
try oneConn.execute(sql: """
|
|
||||||
CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT);
|
|
||||||
""")
|
|
||||||
|
|
||||||
try oneConn.beginTransaction()
|
|
||||||
try oneConn.execute(sql: """
|
|
||||||
INSERT INTO test (value) VALUES ('first');
|
|
||||||
""")
|
|
||||||
|
|
||||||
#expect(
|
|
||||||
throws: SQLiteError(
|
|
||||||
code: SQLITE_BUSY,
|
|
||||||
message: "database is locked"
|
|
||||||
),
|
|
||||||
performing: {
|
|
||||||
twoConn.busyTimeout = 0
|
|
||||||
try twoConn.execute(sql: """
|
|
||||||
INSERT INTO test (value) VALUES ('second');
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
try oneConn.rollbackTransaction()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func testApplicationID() throws {
|
@Test func testApplicationID() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
#expect(connection.applicationID == 0)
|
#expect(connection.applicationID == 0)
|
||||||
@@ -133,8 +90,7 @@ struct ConnectionTests {
|
|||||||
|
|
||||||
@Test func testForeignKeys() throws {
|
@Test func testForeignKeys() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
#expect(connection.foreignKeys == false)
|
#expect(connection.foreignKeys == false)
|
||||||
@@ -147,15 +103,12 @@ struct ConnectionTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func testJournalMode() throws {
|
@Test func testJournalMode() throws {
|
||||||
let url = FileManager.default.temporaryDirectory
|
let dir = FileManager.default.temporaryDirectory
|
||||||
.appendingPathComponent(UUID().uuidString)
|
let file = UUID().uuidString
|
||||||
.appendingPathExtension("sqlite")
|
let path = dir.appending(component: file).path
|
||||||
defer { try? FileManager.default.removeItem(at: url) }
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
|
|
||||||
let connection = try Connection(
|
let connection = try Connection(path: path, options: [.create, .readwrite])
|
||||||
location: .file(path: url.path),
|
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.journalMode = .delete
|
connection.journalMode = .delete
|
||||||
#expect(try connection.get(pragma: .journalMode) == JournalMode.delete)
|
#expect(try connection.get(pragma: .journalMode) == JournalMode.delete)
|
||||||
@@ -166,8 +119,7 @@ struct ConnectionTests {
|
|||||||
|
|
||||||
@Test func testSynchronous() throws {
|
@Test func testSynchronous() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
connection.synchronous = .normal
|
connection.synchronous = .normal
|
||||||
@@ -179,8 +131,7 @@ struct ConnectionTests {
|
|||||||
|
|
||||||
@Test func testUserVersion() throws {
|
@Test func testUserVersion() throws {
|
||||||
let connection = try Connection(
|
let connection = try Connection(
|
||||||
location: .inMemory,
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
options: [.create, .readwrite]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
connection.userVersion = 42
|
connection.userVersion = 42
|
||||||
@@ -190,88 +141,265 @@ struct ConnectionTests {
|
|||||||
#expect(connection.userVersion == 13)
|
#expect(connection.userVersion == 13)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(arguments: [
|
@Test(arguments: ["main", nil])
|
||||||
(TestScalarFunc.self, TestScalarFunc.name),
|
func applyKeyEncrypt(_ name: String?) throws {
|
||||||
(TestAggregateFunc.self, TestAggregateFunc.name)
|
let dir = FileManager.default.temporaryDirectory
|
||||||
] as [(Function.Type, String)])
|
let file = UUID().uuidString
|
||||||
func testAddFunction(
|
let path = dir.appending(component: file).path
|
||||||
_ function: Function.Type,
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
_ name: String
|
|
||||||
) throws {
|
do {
|
||||||
let connection = try Connection(
|
let connection = try Connection(path: path, options: [.create, .readwrite])
|
||||||
location: .inMemory,
|
try connection.apply(.passphrase("test"), name: name)
|
||||||
options: [.create, .readwrite]
|
try connection.execute(sql: "CREATE TABLE t (id INT PRIMARY KEY)")
|
||||||
)
|
}
|
||||||
try connection.execute(sql: """
|
|
||||||
CREATE TABLE items (value INTEGER);
|
do {
|
||||||
INSERT INTO items (value) VALUES (1), (2), (NULL), (3);
|
var connection: OpaquePointer!
|
||||||
""")
|
sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, nil)
|
||||||
try connection.add(function: function)
|
let status = sqlite3_exec(
|
||||||
try connection.execute(sql: "SELECT \(name)(value) FROM items")
|
connection, "SELECT count(*) FROM sqlite_master", nil, nil, nil
|
||||||
|
)
|
||||||
|
#expect(status == SQLITE_NOTADB)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(arguments: [
|
@Test(arguments: ["main", nil])
|
||||||
(TestScalarFunc.self, TestScalarFunc.name),
|
func applyKeyDecrypt(_ name: String?) throws {
|
||||||
(TestAggregateFunc.self, TestAggregateFunc.name)
|
let dir = FileManager.default.temporaryDirectory
|
||||||
] as [(Function.Type, String)])
|
let file = UUID().uuidString
|
||||||
func testRemoveFunction(
|
let path = dir.appending(component: file).path
|
||||||
_ function: Function.Type,
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
_ name: String
|
|
||||||
) throws {
|
do {
|
||||||
let connection = try Connection(
|
var connection: OpaquePointer!
|
||||||
location: .inMemory,
|
let options = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
|
||||||
options: [.create, .readwrite]
|
sqlite3_open_v2(path, &connection, options, nil)
|
||||||
)
|
if let name {
|
||||||
try connection.execute(sql: """
|
sqlite3_key_v2(connection, name, "test", Int32("test".utf8.count))
|
||||||
CREATE TABLE items (value INTEGER);
|
} else {
|
||||||
INSERT INTO items (value) VALUES (1), (2), (NULL), (3);
|
sqlite3_key(connection, "test", Int32("test".utf8.count))
|
||||||
""")
|
|
||||||
try connection.add(function: function)
|
|
||||||
try connection.remove(function: function)
|
|
||||||
#expect(
|
|
||||||
throws: SQLiteError(
|
|
||||||
code: SQLITE_ERROR,
|
|
||||||
message: "no such function: \(name)"
|
|
||||||
),
|
|
||||||
performing: {
|
|
||||||
try connection.execute(sql: """
|
|
||||||
SELECT \(name)(value) FROM items
|
|
||||||
""")
|
|
||||||
}
|
}
|
||||||
|
sqlite3_exec(
|
||||||
|
connection, "CREATE TABLE t (id INT PRIMARY KEY)", nil, nil, nil
|
||||||
|
)
|
||||||
|
sqlite3_close_v2(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let connection = try Connection(path: path, options: [.readwrite])
|
||||||
|
try connection.apply(.passphrase("test"), name: name)
|
||||||
|
try connection.execute(sql: "SELECT count(*) FROM sqlite_master")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(arguments: ["main", nil])
|
||||||
|
func applyKeyInvalid(_ name: String?) throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
)
|
)
|
||||||
|
#expect(
|
||||||
|
throws: SQLiteError(code: SQLITE_MISUSE, message: ""),
|
||||||
|
performing: { try connection.apply(.passphrase(""), name: name) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(arguments: ["main", nil])
|
||||||
|
func rekey(_ name: String?) throws {
|
||||||
|
let dir = FileManager.default.temporaryDirectory
|
||||||
|
let file = UUID().uuidString
|
||||||
|
let path = dir.appending(component: file).path
|
||||||
|
defer { try? FileManager.default.removeItem(atPath: path) }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let connection = try Connection(path: path, options: [.create, .readwrite])
|
||||||
|
try connection.apply(.passphrase("old-test"), name: name)
|
||||||
|
try connection.execute(sql: "CREATE TABLE t (id INT PRIMARY KEY)")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let connection = try Connection(path: path, options: [.create, .readwrite])
|
||||||
|
try connection.apply(.passphrase("old-test"), name: name)
|
||||||
|
try connection.rekey(.passphrase("new-test"), name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let connection = try Connection(path: path, options: [.readwrite])
|
||||||
|
try connection.apply(.passphrase("new-test"), name: name)
|
||||||
|
try connection.execute(sql: "SELECT count(*) FROM sqlite_master")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(arguments: ["main", nil])
|
||||||
|
func rekeyInvalid(_ name: String?) throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
try connection.apply(.passphrase("test"), name: name)
|
||||||
|
try connection.execute(sql: "CREATE TABLE t (id INT PRIMARY KEY)")
|
||||||
|
|
||||||
|
#expect(
|
||||||
|
throws: SQLiteError(code: SQLITE_ERROR, message: ""),
|
||||||
|
performing: { try connection.rekey(.passphrase(""), name: name) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func addDelegate() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
try connection.execute(sql: "CREATE TABLE t (id INT PRIMARY KEY)")
|
||||||
|
|
||||||
|
let delegate = ConnectionDelegate()
|
||||||
|
connection.add(delegate: delegate)
|
||||||
|
|
||||||
|
try connection.execute(sql: "INSERT INTO t (id) VALUES (1)")
|
||||||
|
#expect(delegate.didUpdate)
|
||||||
|
#expect(delegate.willCommit)
|
||||||
|
#expect(delegate.didRollback == false)
|
||||||
|
|
||||||
|
delegate.reset()
|
||||||
|
delegate.error = SQLiteError(code: -1, message: "")
|
||||||
|
|
||||||
|
try? connection.execute(sql: "INSERT INTO t (id) VALUES (2)")
|
||||||
|
#expect(delegate.didUpdate)
|
||||||
|
#expect(delegate.willCommit)
|
||||||
|
#expect(delegate.didRollback)
|
||||||
|
|
||||||
|
delegate.reset()
|
||||||
|
connection.remove(delegate: delegate)
|
||||||
|
|
||||||
|
try connection.execute(sql: "INSERT INTO t (id) VALUES (3)")
|
||||||
|
#expect(delegate.didUpdate == false)
|
||||||
|
#expect(delegate.willCommit == false)
|
||||||
|
#expect(delegate.didRollback == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func addTraceDelegate() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
try connection.execute(sql: "CREATE TABLE t (id INT PRIMARY KEY)")
|
||||||
|
|
||||||
|
let delegate = ConnectionTraceDelegate()
|
||||||
|
connection.add(trace: delegate)
|
||||||
|
|
||||||
|
try connection.execute(sql: "INSERT INTO t (id) VALUES (:id)")
|
||||||
|
#expect(delegate.expandedSQL == "INSERT INTO t (id) VALUES (NULL)")
|
||||||
|
#expect(delegate.unexpandedSQL == "INSERT INTO t (id) VALUES (:id)")
|
||||||
|
|
||||||
|
delegate.reset()
|
||||||
|
connection.remove(trace: delegate)
|
||||||
|
|
||||||
|
try connection.execute(sql: "INSERT INTO t (id) VALUES (:id)")
|
||||||
|
#expect(delegate.expandedSQL == nil)
|
||||||
|
#expect(delegate.unexpandedSQL == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func addFunction() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
|
||||||
|
try connection.add(function: TestFunction.self)
|
||||||
|
#expect(TestFunction.isInstalled)
|
||||||
|
|
||||||
|
try connection.remove(function: TestFunction.self)
|
||||||
|
#expect(TestFunction.isInstalled == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func beginTransaction() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
#expect(connection.isAutocommit)
|
||||||
|
|
||||||
|
try connection.beginTransaction()
|
||||||
|
#expect(connection.isAutocommit == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func commitTransaction() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
#expect(connection.isAutocommit)
|
||||||
|
|
||||||
|
try connection.beginTransaction()
|
||||||
|
try connection.commitTransaction()
|
||||||
|
#expect(connection.isAutocommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func rollbackTransaction() throws {
|
||||||
|
let connection = try Connection(
|
||||||
|
location: .inMemory, options: [.create, .readwrite]
|
||||||
|
)
|
||||||
|
#expect(connection.isAutocommit)
|
||||||
|
|
||||||
|
try connection.beginTransaction()
|
||||||
|
try connection.rollbackTransaction()
|
||||||
|
#expect(connection.isAutocommit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ConnectionTests {
|
private extension ConnectionTests {
|
||||||
final class TestScalarFunc: Function.Scalar {
|
final class ConnectionDelegate: DataLiteCore.ConnectionDelegate {
|
||||||
override class var argc: Int32 { 1 }
|
var error: Error?
|
||||||
override class var name: String { "TO_STR" }
|
|
||||||
override class var options: Options {
|
var didUpdate = false
|
||||||
[.deterministic, .innocuous]
|
var willCommit = false
|
||||||
|
var didRollback = false
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
didUpdate = false
|
||||||
|
willCommit = false
|
||||||
|
didRollback = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override class func invoke(args: any ArgumentsProtocol) throws -> SQLiteRepresentable? {
|
func connection(
|
||||||
args[0].description
|
_ connection: any ConnectionProtocol,
|
||||||
|
didUpdate action: SQLiteAction
|
||||||
|
) {
|
||||||
|
didUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectionWillCommit(_ connection: any ConnectionProtocol) throws {
|
||||||
|
willCommit = true
|
||||||
|
if let error { throw error }
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectionDidRollback(_ connection: any ConnectionProtocol) {
|
||||||
|
didRollback = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class TestAggregateFunc: Function.Aggregate {
|
final class ConnectionTraceDelegate: DataLiteCore.ConnectionTraceDelegate {
|
||||||
override class var argc: Int32 { 1 }
|
var expandedSQL: String?
|
||||||
override class var name: String { "MY_COUNT" }
|
var unexpandedSQL: String?
|
||||||
override class var options: Options {
|
|
||||||
[.deterministic, .innocuous]
|
func reset() {
|
||||||
|
expandedSQL = nil
|
||||||
|
unexpandedSQL = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private var count: Int = 0
|
func connection(_ connection: any ConnectionProtocol, trace sql: Trace) {
|
||||||
|
expandedSQL = sql.expandedSQL
|
||||||
|
unexpandedSQL = sql.unexpandedSQL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func step(args: any ArgumentsProtocol) throws {
|
final class TestFunction: DataLiteCore.Function {
|
||||||
if args[0] != .null {
|
nonisolated(unsafe) static var isInstalled = false
|
||||||
count += 1
|
|
||||||
}
|
override class func install(
|
||||||
|
db connection: OpaquePointer
|
||||||
|
) throws(SQLiteError) {
|
||||||
|
isInstalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func finalize() throws -> SQLiteRepresentable? {
|
override class func uninstall(
|
||||||
count
|
db connection: OpaquePointer
|
||||||
|
) throws(SQLiteError) {
|
||||||
|
isInstalled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import DataLiteC
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct FunctionOptionsTests {
|
struct FunctionOptionsTests {
|
||||||
@Test func testSingleOption() {
|
@Test func singleOption() {
|
||||||
#expect(Function.Options.deterministic.rawValue == SQLITE_DETERMINISTIC)
|
#expect(Function.Options.deterministic.rawValue == SQLITE_DETERMINISTIC)
|
||||||
#expect(Function.Options.directonly.rawValue == SQLITE_DIRECTONLY)
|
#expect(Function.Options.directonly.rawValue == SQLITE_DIRECTONLY)
|
||||||
#expect(Function.Options.innocuous.rawValue == SQLITE_INNOCUOUS)
|
#expect(Function.Options.innocuous.rawValue == SQLITE_INNOCUOUS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testMultipleOptions() {
|
@Test func multipleOptions() {
|
||||||
let options: Function.Options = [.deterministic, .directonly]
|
let options: Function.Options = [.deterministic, .directonly]
|
||||||
#expect(options.contains(.deterministic))
|
#expect(options.contains(.deterministic))
|
||||||
#expect(options.contains(.directonly))
|
#expect(options.contains(.directonly))
|
||||||
#expect(options.contains(.innocuous) == false)
|
#expect(options.contains(.innocuous) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testEqualityAndHashability() {
|
@Test func equalityAndHashability() {
|
||||||
let options1: Function.Options = [.deterministic, .innocuous]
|
let options1: Function.Options = [.deterministic, .innocuous]
|
||||||
let options2: Function.Options = [.deterministic, .innocuous]
|
let options2: Function.Options = [.deterministic, .innocuous]
|
||||||
#expect(options1 == options2)
|
#expect(options1 == options2)
|
||||||
@@ -26,14 +26,14 @@ struct FunctionOptionsTests {
|
|||||||
#expect(hash1 == hash2)
|
#expect(hash1 == hash2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testEmptyOptions() {
|
@Test func emptyOptions() {
|
||||||
let options = Function.Options(rawValue: 0)
|
let options = Function.Options(rawValue: 0)
|
||||||
#expect(options.contains(.deterministic) == false)
|
#expect(options.contains(.deterministic) == false)
|
||||||
#expect(options.contains(.directonly) == false)
|
#expect(options.contains(.directonly) == false)
|
||||||
#expect(options.contains(.innocuous) == false)
|
#expect(options.contains(.innocuous) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testRawValueInitialization() {
|
@Test func rawValueInitialization() {
|
||||||
let rawValue: Int32 = SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS
|
let rawValue: Int32 = SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS
|
||||||
let options = Function.Options(rawValue: rawValue)
|
let options = Function.Options(rawValue: rawValue)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ struct FunctionOptionsTests {
|
|||||||
#expect(options.contains(.directonly) == false)
|
#expect(options.contains(.directonly) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testAddingAndRemovingOptions() {
|
@Test func addingAndRemovingOptions() {
|
||||||
var options: Function.Options = []
|
var options: Function.Options = []
|
||||||
|
|
||||||
options.insert(.deterministic)
|
options.insert(.deterministic)
|
||||||
@@ -55,4 +55,3 @@ struct FunctionOptionsTests {
|
|||||||
#expect(options.contains(.deterministic) == false)
|
#expect(options.contains(.deterministic) == false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
82
Tests/DataLiteCoreTests/Classes/Function+RegexpTests.swift
Normal file
82
Tests/DataLiteCoreTests/Classes/Function+RegexpTests.swift
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
import DataLiteCore
|
||||||
|
|
||||||
|
struct FunctionRegexpTests {
|
||||||
|
@Test func metadata() {
|
||||||
|
#expect(Regexp.argc == 2)
|
||||||
|
#expect(Regexp.name == "REGEXP")
|
||||||
|
#expect(Regexp.options == [.deterministic, .innocuous])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func invalidArguments() {
|
||||||
|
let arguments: Arguments = [.int(1), .text("value")]
|
||||||
|
#expect(
|
||||||
|
performing: {
|
||||||
|
try Regexp.invoke(args: arguments)
|
||||||
|
},
|
||||||
|
throws: {
|
||||||
|
switch $0 {
|
||||||
|
case Regexp.Error.invalidArguments:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func invalidPattern() {
|
||||||
|
let arguments: Arguments = [.text("("), .text("value")]
|
||||||
|
#expect(
|
||||||
|
performing: {
|
||||||
|
try Regexp.invoke(args: arguments)
|
||||||
|
},
|
||||||
|
throws: {
|
||||||
|
switch $0 {
|
||||||
|
case Regexp.Error.regexError:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func matchesPattern() throws {
|
||||||
|
let arguments: Arguments = [.text("foo.*"), .text("foobar")]
|
||||||
|
#expect(try Regexp.invoke(args: arguments) as? Bool == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func doesNotMatchPattern() throws {
|
||||||
|
let arguments: Arguments = [.text("bar.*"), .text("foobar")]
|
||||||
|
#expect(try Regexp.invoke(args: arguments) as? Bool == false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension FunctionRegexpTests {
|
||||||
|
typealias Regexp = Function.Regexp
|
||||||
|
|
||||||
|
struct Arguments: ArgumentsProtocol, ExpressibleByArrayLiteral {
|
||||||
|
private let values: [SQLiteValue]
|
||||||
|
|
||||||
|
var startIndex: Int { values.startIndex }
|
||||||
|
var endIndex: Int { values.endIndex }
|
||||||
|
|
||||||
|
init(_ values: [SQLiteValue]) {
|
||||||
|
self.values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
init(arrayLiteral elements: SQLiteValue...) {
|
||||||
|
self.values = elements
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(index: Int) -> SQLiteValue {
|
||||||
|
values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(after i: Int) -> Int {
|
||||||
|
values.index(after: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@ import DataLiteC
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct StatementOptionsTests {
|
struct StatementOptionsTests {
|
||||||
@Test func testPersistentOptions() {
|
@Test func persistentOptions() {
|
||||||
#expect(Statement.Options.persistent.rawValue == UInt32(SQLITE_PREPARE_PERSISTENT))
|
#expect(Statement.Options.persistent.rawValue == UInt32(SQLITE_PREPARE_PERSISTENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testNoVtabOptions() {
|
@Test func noVtabOptions() {
|
||||||
#expect(Statement.Options.noVtab.rawValue == UInt32(SQLITE_PREPARE_NO_VTAB))
|
#expect(Statement.Options.noVtab.rawValue == UInt32(SQLITE_PREPARE_NO_VTAB))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testCombineOptions() {
|
@Test func combineOptions() {
|
||||||
let options: Statement.Options = [.persistent, .noVtab]
|
let options: Statement.Options = [.persistent, .noVtab]
|
||||||
let expected = UInt32(SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NO_VTAB)
|
let expected = UInt32(SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NO_VTAB)
|
||||||
#expect(options.contains(.persistent))
|
#expect(options.contains(.persistent))
|
||||||
@@ -20,19 +20,19 @@ struct StatementOptionsTests {
|
|||||||
#expect(options.rawValue == expected)
|
#expect(options.rawValue == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInitWithUInt32RawValue() {
|
@Test func initWithUInt32RawValue() {
|
||||||
let raw = UInt32(SQLITE_PREPARE_PERSISTENT)
|
let raw = UInt32(SQLITE_PREPARE_PERSISTENT)
|
||||||
let options = Statement.Options(rawValue: raw)
|
let options = Statement.Options(rawValue: raw)
|
||||||
#expect(options == .persistent)
|
#expect(options == .persistent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInitWithInt32RawValue() {
|
@Test func initWithInt32RawValue() {
|
||||||
let raw = Int32(SQLITE_PREPARE_NO_VTAB)
|
let raw = Int32(SQLITE_PREPARE_NO_VTAB)
|
||||||
let options = Statement.Options(rawValue: raw)
|
let options = Statement.Options(rawValue: raw)
|
||||||
#expect(options == .noVtab)
|
#expect(options == .noVtab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testEmptySetRawValueIsZero() {
|
@Test func emptySetRawValueIsZero() {
|
||||||
let empty: Statement.Options = []
|
let empty: Statement.Options = []
|
||||||
#expect(empty.rawValue == 0)
|
#expect(empty.rawValue == 0)
|
||||||
#expect(!empty.contains(.persistent))
|
#expect(!empty.contains(.persistent))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ final class StatementTests {
|
|||||||
sqlite3_close_v2(connection)
|
sqlite3_close_v2(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInitWithError() throws {
|
@Test func initWithError() throws {
|
||||||
#expect(
|
#expect(
|
||||||
throws: SQLiteError(
|
throws: SQLiteError(
|
||||||
code: SQLITE_ERROR,
|
code: SQLITE_ERROR,
|
||||||
@@ -46,19 +46,22 @@ final class StatementTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testParameterCount() throws {
|
@Test func sqlString() throws {
|
||||||
let sql = "SELECT * FROM t WHERE id = ? AND s = ?"
|
let sql = "SELECT * FROM t WHERE id = ?"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.parameterCount() == 2)
|
#expect(stmt.sql == sql)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testZeroParameterCount() throws {
|
@Test(arguments: [
|
||||||
let sql = "SELECT * FROM t"
|
("SELECT * FROM t WHERE id = ? AND s = ?", 2),
|
||||||
|
("SELECT * FROM t WHERE id = 1 AND s = ''", 0)
|
||||||
|
])
|
||||||
|
func parameterCount(_ sql: String, _ expanded: Int32) throws {
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.parameterCount() == 0)
|
#expect(stmt.parameterCount() == expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testParameterIndexByName() throws {
|
@Test func parameterIndexByName() throws {
|
||||||
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.parameterIndexBy(":id") == 1)
|
#expect(stmt.parameterIndexBy(":id") == 1)
|
||||||
@@ -66,7 +69,7 @@ final class StatementTests {
|
|||||||
#expect(stmt.parameterIndexBy(":invalid") == 0)
|
#expect(stmt.parameterIndexBy(":invalid") == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testParameterNameByIndex() throws {
|
@Test func parameterNameByIndex() throws {
|
||||||
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.parameterNameBy(1) == ":id")
|
#expect(stmt.parameterNameBy(1) == ":id")
|
||||||
@@ -74,20 +77,36 @@ final class StatementTests {
|
|||||||
#expect(stmt.parameterNameBy(3) == nil)
|
#expect(stmt.parameterNameBy(3) == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testBindValueAtIndex() throws {
|
@Test func bindValueAtIndex() throws {
|
||||||
let sql = "SELECT * FROM t where id = ?"
|
let sql = "SELECT * FROM t WHERE id = ?"
|
||||||
|
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
|
|
||||||
try stmt.bind(.int(42), at: 1)
|
try stmt.bind(.int(42), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42")
|
||||||
|
|
||||||
try stmt.bind(.real(42), at: 1)
|
try stmt.bind(.real(42), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42.0")
|
||||||
|
|
||||||
try stmt.bind(.text("42"), at: 1)
|
try stmt.bind(.text("42"), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = '42'")
|
||||||
|
|
||||||
try stmt.bind(.blob(Data([0x42])), at: 1)
|
try stmt.bind(.blob(Data([0x42])), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = x'42'")
|
||||||
|
|
||||||
try stmt.bind(.null, at: 1)
|
try stmt.bind(.null, at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
|
|
||||||
try stmt.bind(TestValue(value: 42), at: 1)
|
try stmt.bind(TestValue(value: 42), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42")
|
||||||
|
|
||||||
try stmt.bind(TestValue?.none, at: 1)
|
try stmt.bind(TestValue?.none, at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testErrorBindValueAtIndex() throws {
|
@Test func errorBindValueAtIndex() throws {
|
||||||
let sql = "SELECT * FROM t where id = ?"
|
let sql = "SELECT * FROM t WHERE id = ?"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(
|
#expect(
|
||||||
throws: SQLiteError(
|
throws: SQLiteError(
|
||||||
@@ -100,20 +119,36 @@ final class StatementTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testBindValueByName() throws {
|
@Test func bindValueByName() throws {
|
||||||
let sql = "SELECT * FROM t where id = :id"
|
let sql = "SELECT * FROM t WHERE id = :id"
|
||||||
|
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
|
|
||||||
try stmt.bind(.int(42), by: ":id")
|
try stmt.bind(.int(42), by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42")
|
||||||
|
|
||||||
try stmt.bind(.real(42), by: ":id")
|
try stmt.bind(.real(42), by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42.0")
|
||||||
|
|
||||||
try stmt.bind(.text("42"), by: ":id")
|
try stmt.bind(.text("42"), by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = '42'")
|
||||||
|
|
||||||
try stmt.bind(.blob(Data([0x42])), by: ":id")
|
try stmt.bind(.blob(Data([0x42])), by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = x'42'")
|
||||||
|
|
||||||
try stmt.bind(.null, by: ":id")
|
try stmt.bind(.null, by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
|
|
||||||
try stmt.bind(TestValue(value: 42), by: ":id")
|
try stmt.bind(TestValue(value: 42), by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42")
|
||||||
|
|
||||||
try stmt.bind(TestValue?.none, by: ":id")
|
try stmt.bind(TestValue?.none, by: ":id")
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testErrorBindValueByName() throws {
|
@Test func errorBindValueByName() throws {
|
||||||
let sql = "SELECT * FROM t where id = :id"
|
let sql = "SELECT * FROM t WHERE id = :id"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(
|
#expect(
|
||||||
throws: SQLiteError(
|
throws: SQLiteError(
|
||||||
@@ -126,14 +161,52 @@ final class StatementTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testStepOneRow() throws {
|
@Test func bindRow() throws {
|
||||||
let sql = "SELECT 1 where 1"
|
let row: SQLiteRow = ["id": .int(42), "name": .text("Alice")]
|
||||||
|
let sql = "SELECT * FROM t WHERE id = :id AND s = :name"
|
||||||
|
|
||||||
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL AND s = NULL")
|
||||||
|
|
||||||
|
try stmt.bind(row)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42 AND s = 'Alice'")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func errorBindRow() throws {
|
||||||
|
let row: SQLiteRow = ["name": .text("Alice")]
|
||||||
|
let stmt = try Statement(
|
||||||
|
db: connection, sql: "SELECT * FROM t", options: []
|
||||||
|
)
|
||||||
|
#expect(
|
||||||
|
throws: SQLiteError(
|
||||||
|
code: SQLITE_RANGE,
|
||||||
|
message: "column index out of range"
|
||||||
|
),
|
||||||
|
performing: {
|
||||||
|
try stmt.bind(row)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func clearBindings() throws {
|
||||||
|
let sql = "SELECT * FROM t WHERE id = :id"
|
||||||
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
|
|
||||||
|
try stmt.bind(.int(42), at: 1)
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = 42")
|
||||||
|
|
||||||
|
try stmt.clearBindings()
|
||||||
|
#expect(stmt.expandedSQL == "SELECT * FROM t WHERE id = NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func stepOneRow() throws {
|
||||||
|
let sql = "SELECT 1 WHERE 1"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(try stmt.step())
|
#expect(try stmt.step())
|
||||||
#expect(try stmt.step() == false)
|
#expect(try stmt.step() == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testStepMultipleRows() throws {
|
@Test func stepMultipleRows() throws {
|
||||||
sqlite3_exec(connection, "INSERT INTO t(n) VALUES (1),(2),(3)", nil, nil, nil)
|
sqlite3_exec(connection, "INSERT INTO t(n) VALUES (1),(2),(3)", nil, nil, nil)
|
||||||
let sql = "SELECT id FROM t ORDER BY id"
|
let sql = "SELECT id FROM t ORDER BY id"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
@@ -143,13 +216,13 @@ final class StatementTests {
|
|||||||
#expect(try stmt.step() == false)
|
#expect(try stmt.step() == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testStepNoRows() throws {
|
@Test func stepNoRows() throws {
|
||||||
let sql = "SELECT 1 WHERE 0"
|
let sql = "SELECT 1 WHERE 0"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(try stmt.step() == false)
|
#expect(try stmt.step() == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testStepWithError() throws {
|
@Test func stepWithError() throws {
|
||||||
sqlite3_exec(connection, "INSERT INTO t(id, n) VALUES (1, 10)", nil, nil, nil)
|
sqlite3_exec(connection, "INSERT INTO t(id, n) VALUES (1, 10)", nil, nil, nil)
|
||||||
let sql = "INSERT INTO t(id, n) VALUES (?, ?)"
|
let sql = "INSERT INTO t(id, n) VALUES (?, ?)"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
@@ -166,13 +239,52 @@ final class StatementTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testColumnCount() throws {
|
@Test func executeRows() throws {
|
||||||
|
let rows: [SQLiteRow] = [
|
||||||
|
[
|
||||||
|
"id": .int(1),
|
||||||
|
"n": .int(42),
|
||||||
|
"r": .real(3.14),
|
||||||
|
"s": .text("Test"),
|
||||||
|
"b": .blob(Data([0x42]))
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"id": .int(2),
|
||||||
|
"n": .null,
|
||||||
|
"r": .null,
|
||||||
|
"s": .null,
|
||||||
|
"b": .null
|
||||||
|
]
|
||||||
|
]
|
||||||
|
let sql = "INSERT INTO t(id, n, r, s, b) VALUES (:id, :n, :r, :s, :b)"
|
||||||
|
try Statement(db: connection, sql: sql, options: []).execute(rows)
|
||||||
|
|
||||||
|
let stmt = try Statement(db: connection, sql: "SELECT * FROM t", options: [])
|
||||||
|
|
||||||
|
#expect(try stmt.step())
|
||||||
|
#expect(stmt.currentRow() == rows[0])
|
||||||
|
|
||||||
|
#expect(try stmt.step())
|
||||||
|
#expect(stmt.currentRow() == rows[1])
|
||||||
|
|
||||||
|
#expect(try stmt.step() == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func executeEmptyRows() throws {
|
||||||
|
let sql = "INSERT INTO t(id, n, r, s, b) VALUES (:id, :n, :r, :s, :b)"
|
||||||
|
try Statement(db: connection, sql: sql, options: []).execute([])
|
||||||
|
|
||||||
|
let stmt = try Statement(db: connection, sql: "SELECT * FROM t", options: [])
|
||||||
|
#expect(try stmt.step() == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func columnCount() throws {
|
||||||
let sql = "SELECT * FROM t"
|
let sql = "SELECT * FROM t"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.columnCount() == 5)
|
#expect(stmt.columnCount() == 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testColumnName() throws {
|
@Test func columnName() throws {
|
||||||
let sql = "SELECT * FROM t"
|
let sql = "SELECT * FROM t"
|
||||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
#expect(stmt.columnName(at: 0) == "id")
|
#expect(stmt.columnName(at: 0) == "id")
|
||||||
@@ -180,10 +292,13 @@ final class StatementTests {
|
|||||||
#expect(stmt.columnName(at: 2) == "r")
|
#expect(stmt.columnName(at: 2) == "r")
|
||||||
#expect(stmt.columnName(at: 3) == "s")
|
#expect(stmt.columnName(at: 3) == "s")
|
||||||
#expect(stmt.columnName(at: 4) == "b")
|
#expect(stmt.columnName(at: 4) == "b")
|
||||||
|
#expect(stmt.columnName(at: 5) == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testColumnValueAtIndex() throws {
|
@Test func columnValueAtIndex() throws {
|
||||||
sqlite3_exec(connection, """
|
sqlite3_exec(
|
||||||
|
connection,
|
||||||
|
"""
|
||||||
INSERT INTO t (id, n, r, s, b)
|
INSERT INTO t (id, n, r, s, b)
|
||||||
VALUES (10, 42, 3.5, 'hello', x'DEADBEEF')
|
VALUES (10, 42, 3.5, 'hello', x'DEADBEEF')
|
||||||
""", nil, nil, nil
|
""", nil, nil, nil
|
||||||
@@ -201,8 +316,10 @@ final class StatementTests {
|
|||||||
#expect(stmt.columnValue(at: 4) == .blob(Data([0xDE, 0xAD, 0xBE, 0xEF])))
|
#expect(stmt.columnValue(at: 4) == .blob(Data([0xDE, 0xAD, 0xBE, 0xEF])))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testColumnNullValueAtIndex() throws {
|
@Test func columnNullValueAtIndex() throws {
|
||||||
sqlite3_exec(connection, """
|
sqlite3_exec(
|
||||||
|
connection,
|
||||||
|
"""
|
||||||
INSERT INTO t (id) VALUES (10)
|
INSERT INTO t (id) VALUES (10)
|
||||||
""", nil, nil, nil
|
""", nil, nil, nil
|
||||||
)
|
)
|
||||||
@@ -215,6 +332,30 @@ final class StatementTests {
|
|||||||
#expect(stmt.columnValue(at: 1) == .null)
|
#expect(stmt.columnValue(at: 1) == .null)
|
||||||
#expect(stmt.columnValue(at: 1) == TestValue?.none)
|
#expect(stmt.columnValue(at: 1) == TestValue?.none)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func currentRow() throws {
|
||||||
|
sqlite3_exec(
|
||||||
|
connection,
|
||||||
|
"""
|
||||||
|
INSERT INTO t (id, n, r, s, b)
|
||||||
|
VALUES (10, 42, 3.5, 'hello', x'DEADBEEF')
|
||||||
|
""", nil, nil, nil
|
||||||
|
)
|
||||||
|
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"id": .int(10),
|
||||||
|
"n": .int(42),
|
||||||
|
"r": .real(3.5),
|
||||||
|
"s": .text("hello"),
|
||||||
|
"b": .blob(Data([0xDE, 0xAD, 0xBE, 0xEF]))
|
||||||
|
]
|
||||||
|
|
||||||
|
let sql = "SELECT * FROM t WHERE id = 10"
|
||||||
|
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||||
|
|
||||||
|
#expect(try stmt.step())
|
||||||
|
#expect(stmt.currentRow() == row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StatementTests {
|
private extension StatementTests {
|
||||||
|
|||||||
33
Tests/DataLiteCoreTests/Enums/JournalModeTests.swift
Normal file
33
Tests/DataLiteCoreTests/Enums/JournalModeTests.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Testing
|
||||||
|
import DataLiteCore
|
||||||
|
|
||||||
|
struct JournalModeTests {
|
||||||
|
@Test func rawValue() {
|
||||||
|
#expect(JournalMode.delete.rawValue == "DELETE")
|
||||||
|
#expect(JournalMode.truncate.rawValue == "TRUNCATE")
|
||||||
|
#expect(JournalMode.persist.rawValue == "PERSIST")
|
||||||
|
#expect(JournalMode.memory.rawValue == "MEMORY")
|
||||||
|
#expect(JournalMode.wal.rawValue == "WAL")
|
||||||
|
#expect(JournalMode.off.rawValue == "OFF")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func initRawValue() {
|
||||||
|
#expect(JournalMode(rawValue: "DELETE") == .delete)
|
||||||
|
#expect(JournalMode(rawValue: "delete") == .delete)
|
||||||
|
|
||||||
|
#expect(JournalMode(rawValue: "TRUNCATE") == .truncate)
|
||||||
|
#expect(JournalMode(rawValue: "truncate") == .truncate)
|
||||||
|
|
||||||
|
#expect(JournalMode(rawValue: "PERSIST") == .persist)
|
||||||
|
#expect(JournalMode(rawValue: "persist") == .persist)
|
||||||
|
|
||||||
|
#expect(JournalMode(rawValue: "MEMORY") == .memory)
|
||||||
|
#expect(JournalMode(rawValue: "memory") == .memory)
|
||||||
|
|
||||||
|
#expect(JournalMode(rawValue: "WAL") == .wal)
|
||||||
|
#expect(JournalMode(rawValue: "wal") == .wal)
|
||||||
|
|
||||||
|
#expect(JournalMode(rawValue: "OFF") == .off)
|
||||||
|
#expect(JournalMode(rawValue: "off") == .off)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
import DataLiteCore
|
|
||||||
|
|
||||||
class SQLiteActionTests: XCTestCase {
|
|
||||||
func testInsertAction() {
|
|
||||||
let action = SQLiteAction.insert(db: "testDB", table: "users", rowID: 1)
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .insert(let db, let table, let rowID):
|
|
||||||
XCTAssertEqual(db, "testDB", "Database name should be 'testDB'")
|
|
||||||
XCTAssertEqual(table, "users", "Table name should be 'users'")
|
|
||||||
XCTAssertEqual(rowID, 1, "Row ID should be 1")
|
|
||||||
default:
|
|
||||||
XCTFail("Expected insert action")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdateAction() {
|
|
||||||
let action = SQLiteAction.update(db: "testDB", table: "users", rowID: 1)
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .update(let db, let table, let rowID):
|
|
||||||
XCTAssertEqual(db, "testDB", "Database name should be 'testDB'")
|
|
||||||
XCTAssertEqual(table, "users", "Table name should be 'users'")
|
|
||||||
XCTAssertEqual(rowID, 1, "Row ID should be 1")
|
|
||||||
default:
|
|
||||||
XCTFail("Expected update action")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDeleteAction() {
|
|
||||||
let action = SQLiteAction.delete(db: "testDB", table: "users", rowID: 1)
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .delete(let db, let table, let rowID):
|
|
||||||
XCTAssertEqual(db, "testDB", "Database name should be 'testDB'")
|
|
||||||
XCTAssertEqual(table, "users", "Table name should be 'users'")
|
|
||||||
XCTAssertEqual(rowID, 1, "Row ID should be 1")
|
|
||||||
default:
|
|
||||||
XCTFail("Expected delete action")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,14 +4,14 @@ import DataLiteCore
|
|||||||
|
|
||||||
struct SQLiteValueTests {
|
struct SQLiteValueTests {
|
||||||
@Test(arguments: [1, 42, 1234])
|
@Test(arguments: [1, 42, 1234])
|
||||||
func testSQLiteIntValue(_ value: Int64) {
|
func intSQLiteValue(_ value: Int64) {
|
||||||
let value = SQLiteValue.int(value)
|
let value = SQLiteValue.int(value)
|
||||||
#expect(value.sqliteLiteral == "\(value)")
|
#expect(value.sqliteLiteral == "\(value)")
|
||||||
#expect(value.description == value.sqliteLiteral)
|
#expect(value.description == value.sqliteLiteral)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(arguments: [12, 0.5, 123.99])
|
@Test(arguments: [12, 0.5, 123.99])
|
||||||
func testSQLiteRealValue(_ value: Double) {
|
func realSQLiteValue(_ value: Double) {
|
||||||
let value = SQLiteValue.real(value)
|
let value = SQLiteValue.real(value)
|
||||||
#expect(value.sqliteLiteral == "\(value)")
|
#expect(value.sqliteLiteral == "\(value)")
|
||||||
#expect(value.description == value.sqliteLiteral)
|
#expect(value.description == value.sqliteLiteral)
|
||||||
@@ -24,7 +24,7 @@ struct SQLiteValueTests {
|
|||||||
("O'Reilly", "'O''Reilly'"),
|
("O'Reilly", "'O''Reilly'"),
|
||||||
("It's John's \"book\"", "'It''s John''s \"book\"'")
|
("It's John's \"book\"", "'It''s John''s \"book\"'")
|
||||||
])
|
])
|
||||||
func testSQLiteTextValue(_ value: String, _ expected: String) {
|
func textSQLiteValue(_ value: String, _ expected: String) {
|
||||||
let value = SQLiteValue.text(value)
|
let value = SQLiteValue.text(value)
|
||||||
#expect(value.sqliteLiteral == expected)
|
#expect(value.sqliteLiteral == expected)
|
||||||
#expect(value.description == value.sqliteLiteral)
|
#expect(value.description == value.sqliteLiteral)
|
||||||
@@ -35,13 +35,13 @@ struct SQLiteValueTests {
|
|||||||
(Data([0x00]), "X'00'"),
|
(Data([0x00]), "X'00'"),
|
||||||
(Data([0x00, 0xAB, 0xCD]), "X'00ABCD'")
|
(Data([0x00, 0xAB, 0xCD]), "X'00ABCD'")
|
||||||
])
|
])
|
||||||
func testSQLiteBlobValue(_ value: Data, _ expected: String) {
|
func blobSQLiteValue(_ value: Data, _ expected: String) {
|
||||||
let value = SQLiteValue.blob(value)
|
let value = SQLiteValue.blob(value)
|
||||||
#expect(value.sqliteLiteral == expected)
|
#expect(value.sqliteLiteral == expected)
|
||||||
#expect(value.description == value.sqliteLiteral)
|
#expect(value.description == value.sqliteLiteral)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteNullValue() {
|
@Test func nullSQLiteValue() {
|
||||||
let value = SQLiteValue.null
|
let value = SQLiteValue.null
|
||||||
#expect(value.sqliteLiteral == "NULL")
|
#expect(value.sqliteLiteral == "NULL")
|
||||||
#expect(value.description == value.sqliteLiteral)
|
#expect(value.description == value.sqliteLiteral)
|
||||||
|
|||||||
29
Tests/DataLiteCoreTests/Enums/TransactionTypeTests.swift
Normal file
29
Tests/DataLiteCoreTests/Enums/TransactionTypeTests.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import Testing
|
||||||
|
import DataLiteCore
|
||||||
|
|
||||||
|
struct TransactionTypeTests {
|
||||||
|
@Test func description() {
|
||||||
|
#expect(TransactionType.deferred.description == "DEFERRED")
|
||||||
|
#expect(TransactionType.immediate.description == "IMMEDIATE")
|
||||||
|
#expect(TransactionType.exclusive.description == "EXCLUSIVE")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func rawValue() {
|
||||||
|
#expect(TransactionType.deferred.rawValue == "DEFERRED")
|
||||||
|
#expect(TransactionType.immediate.rawValue == "IMMEDIATE")
|
||||||
|
#expect(TransactionType.exclusive.rawValue == "EXCLUSIVE")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func initRawValue() {
|
||||||
|
#expect(TransactionType(rawValue: "DEFERRED") == .deferred)
|
||||||
|
#expect(TransactionType(rawValue: "deferred") == .deferred)
|
||||||
|
|
||||||
|
#expect(TransactionType(rawValue: "IMMEDIATE") == .immediate)
|
||||||
|
#expect(TransactionType(rawValue: "immediate") == .immediate)
|
||||||
|
|
||||||
|
#expect(TransactionType(rawValue: "EXCLUSIVE") == .exclusive)
|
||||||
|
#expect(TransactionType(rawValue: "exclusive") == .exclusive)
|
||||||
|
|
||||||
|
#expect(TransactionType(rawValue: "SOME_STR") == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,26 @@
|
|||||||
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct BinaryFloatingPointTests {
|
struct BinaryFloatingPointTests {
|
||||||
@Test func testFloatToSQLiteRawValue() {
|
@Test func floatingPointToSQLiteValue() {
|
||||||
let floatValue: Float = 3.14
|
#expect(Float(3.14).sqliteValue == .real(Double(Float(3.14))))
|
||||||
let rawValue = floatValue.sqliteValue
|
#expect(Double(3.14).sqliteValue == .real(3.14))
|
||||||
#expect(rawValue == .real(Double(floatValue)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testDoubleToSQLiteRawValue() {
|
@Test func floatingPointFromSQLiteValue() {
|
||||||
let doubleValue: Double = 3.14
|
#expect(Float(SQLiteValue.real(3.14)) == 3.14)
|
||||||
let rawValue = doubleValue.sqliteValue
|
#expect(Float(SQLiteValue.int(42)) == 42)
|
||||||
#expect(rawValue == .real(doubleValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func testFloatInitializationFromSQLiteRawValue() {
|
#expect(Double(SQLiteValue.real(3.14)) == 3.14)
|
||||||
let realValue: SQLiteValue = .real(3.14)
|
#expect(Double(SQLiteValue.int(42)) == 42)
|
||||||
let floatValue = Float(realValue)
|
|
||||||
#expect(floatValue != nil)
|
|
||||||
#expect(floatValue == 3.14)
|
|
||||||
|
|
||||||
let intValue: SQLiteValue = .int(42)
|
#expect(Float(SQLiteValue.text("42")) == nil)
|
||||||
let floatFromInt = Float(intValue)
|
#expect(Float(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
#expect(floatFromInt != nil)
|
#expect(Float(SQLiteValue.null) == nil)
|
||||||
#expect(floatFromInt == 42.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func testDoubleInitializationFromSQLiteRawValue() {
|
#expect(Double(SQLiteValue.text("42")) == nil)
|
||||||
let realValue: SQLiteValue = .real(3.14)
|
#expect(Double(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
let doubleValue = Double(realValue)
|
#expect(Double(SQLiteValue.null) == nil)
|
||||||
#expect(doubleValue != nil)
|
|
||||||
#expect(doubleValue == 3.14)
|
|
||||||
|
|
||||||
let intValue: SQLiteValue = .int(42)
|
|
||||||
let doubleFromInt = Double(intValue)
|
|
||||||
#expect(doubleFromInt != nil)
|
|
||||||
#expect(doubleFromInt == 42.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func testInitializationFailureFromInvalidSQLiteRawValue() {
|
|
||||||
let nullValue: SQLiteValue = .null
|
|
||||||
#expect(Float(nullValue) == nil)
|
|
||||||
#expect(Double(nullValue) == nil)
|
|
||||||
|
|
||||||
let textValue: SQLiteValue = .text("Invalid")
|
|
||||||
#expect(Float(textValue) == nil)
|
|
||||||
#expect(Double(textValue) == nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct BinaryIntegerTests {
|
struct BinaryIntegerTests {
|
||||||
@Test func testIntegerToSQLiteValue() {
|
@Test func integerToSQLiteValue() {
|
||||||
#expect(Int(42).sqliteValue == .int(42))
|
#expect(Int(42).sqliteValue == .int(42))
|
||||||
#expect(Int8(42).sqliteValue == .int(42))
|
#expect(Int8(42).sqliteValue == .int(42))
|
||||||
#expect(Int16(42).sqliteValue == .int(42))
|
#expect(Int16(42).sqliteValue == .int(42))
|
||||||
@@ -17,7 +17,7 @@ struct BinaryIntegerTests {
|
|||||||
#expect(UInt64(42).sqliteValue == .int(42))
|
#expect(UInt64(42).sqliteValue == .int(42))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testIntegerInitializationFromSQLiteValue() {
|
@Test func integerFromSQLiteValue() {
|
||||||
#expect(Int(SQLiteValue.int(42)) == 42)
|
#expect(Int(SQLiteValue.int(42)) == 42)
|
||||||
#expect(Int8(SQLiteValue.int(42)) == 42)
|
#expect(Int8(SQLiteValue.int(42)) == 42)
|
||||||
#expect(Int16(SQLiteValue.int(42)) == 42)
|
#expect(Int16(SQLiteValue.int(42)) == 42)
|
||||||
@@ -29,15 +29,61 @@ struct BinaryIntegerTests {
|
|||||||
#expect(UInt16(SQLiteValue.int(42)) == 42)
|
#expect(UInt16(SQLiteValue.int(42)) == 42)
|
||||||
#expect(UInt32(SQLiteValue.int(42)) == 42)
|
#expect(UInt32(SQLiteValue.int(42)) == 42)
|
||||||
#expect(UInt64(SQLiteValue.int(42)) == 42)
|
#expect(UInt64(SQLiteValue.int(42)) == 42)
|
||||||
}
|
|
||||||
|
|
||||||
@Test func testInvalidIntegerInitialization() {
|
#expect(Int(SQLiteValue.real(42)) == nil)
|
||||||
#expect(Int(SQLiteValue.real(3.14)) == nil)
|
#expect(Int(SQLiteValue.text("42")) == nil)
|
||||||
#expect(Int8(SQLiteValue.text("test")) == nil)
|
#expect(Int(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
#expect(UInt32(SQLiteValue.blob(Data([0x01, 0x02]))) == nil)
|
#expect(Int(SQLiteValue.null) == nil)
|
||||||
|
|
||||||
// Out-of-range conversion
|
#expect(Int8(SQLiteValue.real(42)) == nil)
|
||||||
let largeValue = Int64.max
|
#expect(Int8(SQLiteValue.text("42")) == nil)
|
||||||
#expect(Int8(exactly: largeValue) == nil)
|
#expect(Int8(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(Int8(SQLiteValue.null) == nil)
|
||||||
|
#expect(Int8(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(Int16(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(Int16(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(Int16(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(Int16(SQLiteValue.null) == nil)
|
||||||
|
#expect(Int16(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(Int32(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(Int32(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(Int32(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(Int32(SQLiteValue.null) == nil)
|
||||||
|
#expect(Int32(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(Int64(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(Int64(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(Int64(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(Int64(SQLiteValue.null) == nil)
|
||||||
|
|
||||||
|
#expect(UInt(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(UInt(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(UInt(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UInt(SQLiteValue.null) == nil)
|
||||||
|
|
||||||
|
#expect(UInt8(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(UInt8(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(UInt8(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UInt8(SQLiteValue.null) == nil)
|
||||||
|
#expect(UInt8(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(UInt16(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(UInt16(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(UInt16(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UInt16(SQLiteValue.null) == nil)
|
||||||
|
#expect(UInt16(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(UInt32(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(UInt32(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(UInt32(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UInt32(SQLiteValue.null) == nil)
|
||||||
|
#expect(UInt32(SQLiteValue.int(Int64.max)) == nil)
|
||||||
|
|
||||||
|
#expect(UInt64(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(UInt64(SQLiteValue.text("42")) == nil)
|
||||||
|
#expect(UInt64(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UInt64(SQLiteValue.null) == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct BoolTests {
|
struct BoolTests {
|
||||||
@Test func testBoolToSQLiteRawValue() {
|
@Test func boolToSQLiteValue() {
|
||||||
#expect(true.sqliteValue == .int(1))
|
#expect(true.sqliteValue == .int(1))
|
||||||
#expect(false.sqliteValue == .int(0))
|
#expect(false.sqliteValue == .int(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToBool() {
|
@Test func boolFromSQLiteValue() {
|
||||||
#expect(Bool(.int(1)) == true)
|
#expect(Bool(SQLiteValue.int(1)) == true)
|
||||||
#expect(Bool(.int(0)) == false)
|
#expect(Bool(SQLiteValue.int(0)) == false)
|
||||||
|
|
||||||
#expect(Bool(.int(-1)) == nil)
|
#expect(Bool(SQLiteValue.int(-1)) == nil)
|
||||||
#expect(Bool(.int(2)) == nil)
|
#expect(Bool(SQLiteValue.int(2)) == nil)
|
||||||
#expect(Bool(.real(1.0)) == nil)
|
#expect(Bool(SQLiteValue.real(1.0)) == nil)
|
||||||
#expect(Bool(.text("true")) == nil)
|
#expect(Bool(SQLiteValue.text("true")) == nil)
|
||||||
#expect(Bool(.blob(Data())) == nil)
|
#expect(Bool(SQLiteValue.blob(Data())) == nil)
|
||||||
#expect(Bool(.null) == nil)
|
#expect(Bool(SQLiteValue.null) == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,18 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct DataSQLiteRawRepresentableTests {
|
struct DataSQLiteRawRepresentableTests {
|
||||||
@Test func testDataToSQLiteRawValue() {
|
@Test func dataToSQLiteValue() {
|
||||||
let data = Data([0x01, 0x02, 0x03])
|
let data = Data([0x01, 0x02, 0x03])
|
||||||
#expect(data.sqliteValue == .blob(data))
|
#expect(data.sqliteValue == .blob(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToData() {
|
@Test func dataFromSQLiteValue() {
|
||||||
let data = Data([0x01, 0x02, 0x03])
|
let data = Data([0x01, 0x02, 0x03])
|
||||||
let rawValue = SQLiteValue.blob(data)
|
#expect(Data(SQLiteValue.blob(data)) == data)
|
||||||
|
|
||||||
#expect(Data(rawValue) == data)
|
#expect(Data(SQLiteValue.int(1)) == nil)
|
||||||
|
#expect(Data(SQLiteValue.real(1.0)) == nil)
|
||||||
#expect(Data(.int(1)) == nil)
|
#expect(Data(SQLiteValue.text("blob")) == nil)
|
||||||
#expect(Data(.real(1.0)) == nil)
|
#expect(Data(SQLiteValue.null) == nil)
|
||||||
#expect(Data(.text("blob")) == nil)
|
|
||||||
#expect(Data(.null) == nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,30 +3,23 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct DateSQLiteRawRepresentableTests {
|
struct DateSQLiteRawRepresentableTests {
|
||||||
@Test func testDateToSQLiteRawValue() {
|
@Test func dateToSQLiteValue() {
|
||||||
let date = Date(timeIntervalSince1970: 1609459200)
|
let date = Date(timeIntervalSince1970: 1609459200)
|
||||||
let formatter = ISO8601DateFormatter()
|
let dateString = "2021-01-01T00:00:00Z"
|
||||||
let dateString = formatter.string(from: date)
|
|
||||||
|
|
||||||
#expect(date.sqliteValue == .text(dateString))
|
#expect(date.sqliteValue == .text(dateString))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToDate() {
|
@Test func dateFromSQLiteValue() {
|
||||||
let date = Date(timeIntervalSince1970: 1609459200)
|
let date = Date(timeIntervalSince1970: 1609459200)
|
||||||
let formatter = ISO8601DateFormatter()
|
let dateString = "2021-01-01T00:00:00Z"
|
||||||
let dateString = formatter.string(from: date)
|
|
||||||
|
|
||||||
let rawText = SQLiteValue.text(dateString)
|
#expect(Date(SQLiteValue.text(dateString)) == date)
|
||||||
#expect(Date(rawText) == date)
|
#expect(Date(SQLiteValue.int(1609459200)) == date)
|
||||||
|
#expect(Date(SQLiteValue.real(1609459200)) == date)
|
||||||
|
|
||||||
let rawInt = SQLiteValue.int(1609459200)
|
#expect(Date(SQLiteValue.blob(Data([0x01, 0x02, 0x03]))) == nil)
|
||||||
#expect(Date(rawInt) == date)
|
#expect(Date(SQLiteValue.null) == nil)
|
||||||
|
#expect(Date(SQLiteValue.text("Invalid date format")) == nil)
|
||||||
let rawReal = SQLiteValue.real(1609459200)
|
|
||||||
#expect(Date(rawReal) == date)
|
|
||||||
|
|
||||||
#expect(Date(.blob(Data([0x01, 0x02, 0x03]))) == nil)
|
|
||||||
#expect(Date(.null) == nil)
|
|
||||||
#expect(Date(.text("Invalid date format")) == nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,30 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct RawRepresentableTests {
|
struct RawRepresentableTests {
|
||||||
@Test func testRawRepresentableToSQLiteRawValue() {
|
@Test func rawRepresentableToSQLiteValue() {
|
||||||
let color: Color = .green
|
#expect(Color.red.sqliteValue == .int(Color.red.rawValue))
|
||||||
#expect(color.sqliteValue == .int(1))
|
#expect(Color.green.sqliteValue == .int(Color.green.rawValue))
|
||||||
|
#expect(Color.blue.sqliteValue == .int(Color.blue.rawValue))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToRawRepresentable() {
|
@Test func rawRepresentableFromSQLiteValue() {
|
||||||
#expect(Color(.int(2)) == .blue)
|
#expect(Color(SQLiteValue.int(0)) == .red)
|
||||||
|
#expect(Color(SQLiteValue.int(1)) == .green)
|
||||||
|
#expect(Color(SQLiteValue.int(2)) == .blue)
|
||||||
|
|
||||||
#expect(Color(.int(42)) == nil)
|
#expect(Color(SQLiteValue.int(42)) == nil)
|
||||||
#expect(Color(.text("red")) == nil)
|
#expect(Color(SQLiteValue.real(0)) == nil)
|
||||||
#expect(Color(.blob(Data([0x01, 0x02]))) == nil)
|
#expect(Color(SQLiteValue.real(1)) == nil)
|
||||||
#expect(Color(.null) == nil)
|
#expect(Color(SQLiteValue.real(2)) == nil)
|
||||||
|
#expect(Color(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(Color(SQLiteValue.text("red")) == nil)
|
||||||
|
#expect(Color(SQLiteValue.blob(Data([0x01, 0x02]))) == nil)
|
||||||
|
#expect(Color(SQLiteValue.null) == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension RawRepresentableTests {
|
private extension RawRepresentableTests {
|
||||||
enum Color: Int, SQLiteRepresentable {
|
enum Color: Int64, SQLiteRepresentable {
|
||||||
case red
|
case red
|
||||||
case green
|
case green
|
||||||
case blue
|
case blue
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct StringTests {
|
struct StringTests {
|
||||||
@Test func testStringToSQLiteRawValue() {
|
@Test func stringToSQLiteValue() {
|
||||||
#expect("Hello, SQLite!".sqliteValue == .text("Hello, SQLite!"))
|
#expect("Hello, SQLite!".sqliteValue == .text("Hello, SQLite!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToString() {
|
@Test func stringFromSQLiteValue() {
|
||||||
#expect(String(SQLiteValue.text("Hello, SQLite!")) == "Hello, SQLite!")
|
#expect(String(SQLiteValue.text("Hello, SQLite!")) == "Hello, SQLite!")
|
||||||
|
|
||||||
#expect(String(SQLiteValue.int(42)) == nil)
|
#expect(String(SQLiteValue.int(42)) == nil)
|
||||||
#expect(String(SQLiteValue.blob(Data([0x01, 0x02]))) == nil)
|
#expect(String(SQLiteValue.real(42)) == nil)
|
||||||
|
#expect(String(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
#expect(String(SQLiteValue.null) == nil)
|
#expect(String(SQLiteValue.null) == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@ import Foundation
|
|||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
struct UUIDTests {
|
struct UUIDTests {
|
||||||
@Test func testUUIDToSQLiteRawValue() {
|
@Test func uuidToSQLiteValue() {
|
||||||
let uuid = UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000")!
|
let uuid = UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000")!
|
||||||
#expect(uuid.sqliteValue == .text("123E4567-E89B-12D3-A456-426614174000"))
|
#expect(uuid.sqliteValue == .text("123E4567-E89B-12D3-A456-426614174000"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testSQLiteRawValueToUUID() {
|
@Test func uuidFromSQLiteValue() {
|
||||||
let raw = SQLiteValue.text("123e4567-e89b-12d3-a456-426614174000")
|
let raw = SQLiteValue.text("123e4567-e89b-12d3-a456-426614174000")
|
||||||
#expect(UUID(raw) == UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000"))
|
#expect(UUID(raw) == UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000"))
|
||||||
|
|
||||||
#expect(UUID(.text("invalid-uuid-string")) == nil)
|
#expect(UUID(SQLiteValue.int(42)) == nil)
|
||||||
#expect(UUID(.int(42)) == nil)
|
#expect(UUID(SQLiteValue.real(42)) == nil)
|
||||||
#expect(UUID(.blob(Data([0x01, 0x02]))) == nil)
|
#expect(UUID(SQLiteValue.text("42")) == nil)
|
||||||
#expect(UUID(.null) == nil)
|
#expect(UUID(SQLiteValue.blob(Data([0x42]))) == nil)
|
||||||
|
#expect(UUID(SQLiteValue.null) == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
import DataLiteCore
|
||||||
|
|
||||||
|
struct ArgumentsProtocolTests {
|
||||||
|
@Test func typedSubscript() {
|
||||||
|
let arguments: Arguments = [
|
||||||
|
.text("one"),
|
||||||
|
.text("two"),
|
||||||
|
.int(42)
|
||||||
|
]
|
||||||
|
|
||||||
|
#expect(arguments[0] == TestModel.one)
|
||||||
|
#expect(arguments[1] == TestModel.two)
|
||||||
|
#expect(arguments[2] as TestModel? == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ArgumentsProtocolTests {
|
||||||
|
enum TestModel: String, SQLiteRepresentable {
|
||||||
|
case one, two
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Arguments: ArgumentsProtocol, ExpressibleByArrayLiteral {
|
||||||
|
private let values: [SQLiteValue]
|
||||||
|
|
||||||
|
var startIndex: Int { values.startIndex }
|
||||||
|
var endIndex: Int { values.endIndex }
|
||||||
|
|
||||||
|
init(_ values: [SQLiteValue]) {
|
||||||
|
self.values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
init(arrayLiteral elements: SQLiteValue...) {
|
||||||
|
self.values = elements
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(index: Int) -> SQLiteValue {
|
||||||
|
values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(after i: Int) -> Int {
|
||||||
|
values.index(after: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,6 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
import DataLiteCore
|
import DataLiteCore
|
||||||
|
|
||||||
private struct BindableStub: SQLiteBindable {
|
|
||||||
let value: SQLiteValue
|
|
||||||
var sqliteValue: SQLiteValue { value }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SQLiteBindableTests {
|
struct SQLiteBindableTests {
|
||||||
@Test(arguments: [
|
@Test(arguments: [
|
||||||
SQLiteValue.int(42),
|
SQLiteValue.int(42),
|
||||||
@@ -15,8 +10,15 @@ struct SQLiteBindableTests {
|
|||||||
SQLiteValue.blob(Data([0x00, 0xAB])),
|
SQLiteValue.blob(Data([0x00, 0xAB])),
|
||||||
SQLiteValue.null
|
SQLiteValue.null
|
||||||
])
|
])
|
||||||
func testDefaultSqliteLiteralPassThrough(_ value: SQLiteValue) {
|
func sqliteLiteral(_ value: SQLiteValue) {
|
||||||
let stub = BindableStub(value: value)
|
let stub = Bindable(value: value)
|
||||||
#expect(stub.sqliteLiteral == value.sqliteLiteral)
|
#expect(stub.sqliteLiteral == value.sqliteLiteral)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension SQLiteBindableTests {
|
||||||
|
struct Bindable: SQLiteBindable {
|
||||||
|
let value: SQLiteValue
|
||||||
|
var sqliteValue: SQLiteValue { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Binary file not shown.
@@ -1,16 +0,0 @@
|
|||||||
-- This is a single-line comment.
|
|
||||||
CREATE TABLE users (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is a multi-line comment.
|
|
||||||
It spans multiple lines and can contain any text.
|
|
||||||
*/
|
|
||||||
INSERT INTO users (id, username, email)
|
|
||||||
VALUES
|
|
||||||
(1, 'john_doe', 'john@example.com'), -- Inserting John Doe
|
|
||||||
/* This is a comment inside a statement */
|
|
||||||
(2, 'jane_doe', 'jane@example.com'); -- Inserting Jane Doe
|
|
||||||
30
Tests/DataLiteCoreTests/Structures/PragmaTests.swift
Normal file
30
Tests/DataLiteCoreTests/Structures/PragmaTests.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import Testing
|
||||||
|
|
||||||
|
@testable import DataLiteCore
|
||||||
|
|
||||||
|
struct PragmaTests {
|
||||||
|
@Test(arguments: [
|
||||||
|
(Pragma.applicationID, "application_id"),
|
||||||
|
(Pragma.foreignKeys, "foreign_keys"),
|
||||||
|
(Pragma.journalMode, "journal_mode"),
|
||||||
|
(Pragma.synchronous, "synchronous"),
|
||||||
|
(Pragma.userVersion, "user_version"),
|
||||||
|
(Pragma.busyTimeout, "busy_timeout")
|
||||||
|
])
|
||||||
|
func predefinedPragmas(_ pragma: Pragma, _ expected: String) {
|
||||||
|
#expect(pragma.rawValue == expected)
|
||||||
|
#expect(pragma.description == expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func initRawValue() {
|
||||||
|
let pragma = Pragma(rawValue: "custom_pragma")
|
||||||
|
#expect(pragma.rawValue == "custom_pragma")
|
||||||
|
#expect(pragma.description == "custom_pragma")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func initStringLiteral() {
|
||||||
|
let pragma: Pragma = "another_pragma"
|
||||||
|
#expect(pragma.rawValue == "another_pragma")
|
||||||
|
#expect(pragma.description == "another_pragma")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,36 @@
|
|||||||
import Foundation
|
|
||||||
import Testing
|
import Testing
|
||||||
import DataLiteC
|
import DataLiteC
|
||||||
|
|
||||||
@testable import DataLiteCore
|
@testable import DataLiteCore
|
||||||
|
|
||||||
struct SQLiteErrorTests {
|
struct SQLiteErrorTests {
|
||||||
@Test func testInitWithConnection() {
|
@Test func initWithConnection() {
|
||||||
var db: OpaquePointer? = nil
|
var connection: OpaquePointer!
|
||||||
defer { sqlite3_close(db) }
|
defer { sqlite3_close(connection) }
|
||||||
sqlite3_open(":memory:", &db)
|
sqlite3_open(":memory:", &connection)
|
||||||
sqlite3_exec(db, "INVALID SQL", nil, nil, nil)
|
sqlite3_exec(connection, "INVALID SQL", nil, nil, nil)
|
||||||
|
|
||||||
let error = SQLiteError(db!)
|
let error = SQLiteError(connection)
|
||||||
#expect(error.code == SQLITE_ERROR)
|
#expect(error.code == SQLITE_ERROR)
|
||||||
#expect(error.message == "near \"INVALID\": syntax error")
|
#expect(error.message == "near \"INVALID\": syntax error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInitWithCodeAndMessage() {
|
@Test func initWithCodeAndMessage() {
|
||||||
let error = SQLiteError(code: 1, message: "Test Error Message")
|
let error = SQLiteError(code: 1, message: "Test Error Message")
|
||||||
#expect(error.code == 1)
|
#expect(error.code == 1)
|
||||||
#expect(error.message == "Test Error Message")
|
#expect(error.message == "Test Error Message")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testDescription() {
|
@Test func description() {
|
||||||
let error = SQLiteError(code: 1, message: "Test Error Message")
|
let error = SQLiteError(code: 1, message: "Test Error Message")
|
||||||
#expect(error.description == "SQLiteError code: 1 message: Test Error Message")
|
#expect(error.description == "SQLiteError(1): Test Error Message")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func equality() {
|
||||||
|
let lhs = SQLiteError(code: 1, message: "First")
|
||||||
|
let rhs = SQLiteError(code: 1, message: "First")
|
||||||
|
let different = SQLiteError(code: 2, message: "Second")
|
||||||
|
#expect(lhs == rhs)
|
||||||
|
#expect(lhs != different)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +1,106 @@
|
|||||||
import XCTest
|
import Testing
|
||||||
import DataLiteCore
|
|
||||||
|
|
||||||
final class SQLiteRowTests: XCTestCase {
|
@testable import DataLiteCore
|
||||||
func testInitEmptyRow() {
|
|
||||||
let row = SQLiteRow()
|
|
||||||
XCTAssertTrue(row.isEmpty)
|
|
||||||
XCTAssertEqual(row.count, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdateColumnPosition() {
|
struct SQLiteRowTests {
|
||||||
|
@Test func subscriptByColumn() {
|
||||||
var row = SQLiteRow()
|
var row = SQLiteRow()
|
||||||
|
#expect(row["name"] == nil)
|
||||||
|
|
||||||
row["name"] = .text("Alice")
|
row["name"] = .text("Alice")
|
||||||
row["age"] = .int(30)
|
#expect(row["name"] == .text("Alice"))
|
||||||
|
|
||||||
row["name"] = .text("Bob")
|
row["name"] = .text("Bob")
|
||||||
|
#expect(row["name"] == .text("Bob"))
|
||||||
XCTAssertEqual(row[0].column, "name")
|
|
||||||
XCTAssertEqual(row[0].value, .text("Bob"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSubscriptByColumn() {
|
|
||||||
var row = SQLiteRow()
|
|
||||||
row["name"] = .text("Alice")
|
|
||||||
|
|
||||||
XCTAssertEqual(row["name"], .text("Alice"))
|
|
||||||
XCTAssertNil(row["age"])
|
|
||||||
|
|
||||||
row["age"] = SQLiteValue.int(30)
|
|
||||||
XCTAssertEqual(row["age"], .int(30))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSubscriptByIndex() {
|
|
||||||
var row = SQLiteRow()
|
|
||||||
row["name"] = .text("Alice")
|
|
||||||
row["age"] = .int(30)
|
|
||||||
|
|
||||||
let firstElement = row[row.startIndex]
|
|
||||||
XCTAssertEqual(firstElement.column, "name")
|
|
||||||
XCTAssertEqual(firstElement.value, .text("Alice"))
|
|
||||||
|
|
||||||
let secondElement = row[row.index(after: row.startIndex)]
|
|
||||||
XCTAssertEqual(secondElement.column, "age")
|
|
||||||
XCTAssertEqual(secondElement.value, .int(30))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDescription() {
|
|
||||||
var row = SQLiteRow()
|
|
||||||
row["name"] = .text("Alice")
|
|
||||||
row["age"] = .int(30)
|
|
||||||
|
|
||||||
let expectedDescription = #"["name": 'Alice', "age": 30]"#
|
|
||||||
XCTAssertEqual(row.description, expectedDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCountAndIsEmpty() {
|
|
||||||
var row = SQLiteRow()
|
|
||||||
XCTAssertTrue(row.isEmpty)
|
|
||||||
XCTAssertEqual(row.count, 0)
|
|
||||||
|
|
||||||
row["name"] = .text("Alice")
|
|
||||||
XCTAssertFalse(row.isEmpty)
|
|
||||||
XCTAssertEqual(row.count, 1)
|
|
||||||
|
|
||||||
row["age"] = .int(30)
|
|
||||||
XCTAssertEqual(row.count, 2)
|
|
||||||
|
|
||||||
row["name"] = nil
|
row["name"] = nil
|
||||||
XCTAssertEqual(row.count, 1)
|
#expect(row["name"] == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIteration() {
|
@Test func subscriptByIndex() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30),
|
||||||
|
"city": .text("Wonderland")
|
||||||
|
]
|
||||||
|
#expect(row[0] == ("name", .text("Alice")))
|
||||||
|
#expect(row[1] == ("age", .int(30)))
|
||||||
|
#expect(row[2] == ("city", .text("Wonderland")))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func columns() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30),
|
||||||
|
"city": .text("Wonderland")
|
||||||
|
]
|
||||||
|
#expect(row.columns == ["name", "age", "city"])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func namedParameters() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30),
|
||||||
|
"city": .text("Wonderland")
|
||||||
|
]
|
||||||
|
#expect(row.namedParameters == [":name", ":age", ":city"])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func containsColumn() {
|
||||||
|
let row: SQLiteRow = ["one": .null]
|
||||||
|
#expect(row.contains("one"))
|
||||||
|
#expect(row.contains("two") == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func description() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30)
|
||||||
|
]
|
||||||
|
#expect(row.description == #"["name": 'Alice', "age": 30]"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func startIndex() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30)
|
||||||
|
]
|
||||||
|
#expect(row.startIndex == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func endIndex() {
|
||||||
|
let row: SQLiteRow = [
|
||||||
|
"name": .text("Alice"),
|
||||||
|
"age": .int(30)
|
||||||
|
]
|
||||||
|
#expect(row.endIndex == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func endIndexEmptyRow() {
|
||||||
|
let row = SQLiteRow()
|
||||||
|
#expect(row.endIndex == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func isEmpty() {
|
||||||
var row = SQLiteRow()
|
var row = SQLiteRow()
|
||||||
row["name"] = .text("Alice")
|
#expect(row.isEmpty)
|
||||||
row["age"] = .int(30)
|
|
||||||
row["city"] = .text("Wonderland")
|
|
||||||
|
|
||||||
var elements: [SQLiteRow.Element] = []
|
row["one"] = .int(1)
|
||||||
for (column, value) in row {
|
#expect(row.isEmpty == false)
|
||||||
elements.append((column, value))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
XCTAssertEqual(elements.count, 3)
|
@Test func count() {
|
||||||
XCTAssertEqual(elements[0].column, "name")
|
var row = SQLiteRow()
|
||||||
XCTAssertEqual(elements[0].value, .text("Alice"))
|
#expect(row.count == 0)
|
||||||
XCTAssertEqual(elements[1].column, "age")
|
|
||||||
XCTAssertEqual(elements[1].value, .int(30))
|
row["one"] = .int(1)
|
||||||
XCTAssertEqual(elements[2].column, "city")
|
#expect(row.count == 1)
|
||||||
XCTAssertEqual(elements[2].value, .text("Wonderland"))
|
}
|
||||||
|
|
||||||
|
@Test func indexAfter() {
|
||||||
|
let row = SQLiteRow()
|
||||||
|
#expect(row.index(after: 0) == 1)
|
||||||
|
#expect(row.index(after: 1) == 2)
|
||||||
|
#expect(row.index(after: 2) == 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user