Refactor entire codebase and rewrite documentation
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import DataLiteC
|
||||
@testable import DataLiteCore
|
||||
|
||||
struct ConnectionErrorTests {
|
||||
@Test func testInitWithConnection() {
|
||||
var db: OpaquePointer? = nil
|
||||
defer { sqlite3_close(db) }
|
||||
sqlite3_open(":memory:", &db)
|
||||
sqlite3_exec(db, "INVALID SQL", nil, nil, nil)
|
||||
|
||||
let error = Connection.Error(db!)
|
||||
#expect(error.code == SQLITE_ERROR)
|
||||
#expect(error.message == "near \"INVALID\": syntax error")
|
||||
}
|
||||
|
||||
@Test func testInitWithCodeAndMessage() {
|
||||
let error = Connection.Error(code: 1, message: "Test Error Message")
|
||||
#expect(error.code == 1)
|
||||
#expect(error.message == "Test Error Message")
|
||||
}
|
||||
|
||||
@Test func testDescription() {
|
||||
let error = Connection.Error(code: 1, message: "Test Error Message")
|
||||
#expect(error.description == "Connection.Error code: 1 message: Test Error Message")
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,4 @@ struct ConnectionLocationTests {
|
||||
let temporaryLocation = Connection.Location.temporary
|
||||
#expect(temporaryLocation.path == "")
|
||||
}
|
||||
|
||||
@Test func testFileLocationInitialization() {
|
||||
let filePath = "/path/to/database.db"
|
||||
let location = Connection.Location.file(path: filePath)
|
||||
switch location {
|
||||
case .file(let path):
|
||||
#expect(path == filePath)
|
||||
default:
|
||||
Issue.record("Expected `.file` case but got \(location)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ struct ConnectionTests {
|
||||
""")
|
||||
|
||||
#expect(
|
||||
throws: Connection.Error(
|
||||
throws: SQLiteError(
|
||||
code: SQLITE_BUSY,
|
||||
message: "database is locked"
|
||||
),
|
||||
@@ -229,7 +229,7 @@ struct ConnectionTests {
|
||||
try connection.add(function: function)
|
||||
try connection.remove(function: function)
|
||||
#expect(
|
||||
throws: Connection.Error(
|
||||
throws: SQLiteError(
|
||||
code: SQLITE_ERROR,
|
||||
message: "no such function: \(name)"
|
||||
),
|
||||
@@ -250,7 +250,7 @@ private extension ConnectionTests {
|
||||
[.deterministic, .innocuous]
|
||||
}
|
||||
|
||||
override class func invoke(args: Arguments) throws -> SQLiteRawRepresentable? {
|
||||
override class func invoke(args: any ArgumentsProtocol) throws -> SQLiteRepresentable? {
|
||||
args[0].description
|
||||
}
|
||||
}
|
||||
@@ -264,13 +264,13 @@ private extension ConnectionTests {
|
||||
|
||||
private var count: Int = 0
|
||||
|
||||
override func step(args: Arguments) throws {
|
||||
override func step(args: any ArgumentsProtocol) throws {
|
||||
if args[0] != .null {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
override func finalize() throws -> SQLiteRawRepresentable? {
|
||||
override func finalize() throws -> SQLiteRepresentable? {
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,41 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import DataLiteCore
|
||||
import DataLiteC
|
||||
import DataLiteCore
|
||||
|
||||
struct StatementOptionsTests {
|
||||
@Test func testOptionsInitialization() {
|
||||
let options: Statement.Options = [.persistent]
|
||||
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab) == false)
|
||||
}
|
||||
|
||||
@Test func testOptionsCombination() {
|
||||
var options: Statement.Options = [.persistent]
|
||||
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab) == false)
|
||||
|
||||
options.insert(.noVtab)
|
||||
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab))
|
||||
}
|
||||
|
||||
@Test func testOptionsRemoval() {
|
||||
var options: Statement.Options = [.persistent, .noVtab]
|
||||
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab))
|
||||
|
||||
options.remove(.noVtab)
|
||||
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab) == false)
|
||||
}
|
||||
|
||||
@Test func testOptionsRawValue() {
|
||||
let options: Statement.Options = [.persistent, .noVtab]
|
||||
let rawOpts = UInt32(SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NO_VTAB)
|
||||
|
||||
#expect(options.rawValue == rawOpts)
|
||||
@Test func testPersistentOptions() {
|
||||
#expect(Statement.Options.persistent.rawValue == UInt32(SQLITE_PREPARE_PERSISTENT))
|
||||
}
|
||||
|
||||
@Test func testNoVtabOptions() {
|
||||
#expect(Statement.Options.noVtab.rawValue == UInt32(SQLITE_PREPARE_NO_VTAB))
|
||||
}
|
||||
|
||||
@Test func testCombineOptions() {
|
||||
let options: Statement.Options = [.persistent, .noVtab]
|
||||
let expected = UInt32(SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NO_VTAB)
|
||||
#expect(options.contains(.persistent))
|
||||
#expect(options.contains(.noVtab))
|
||||
#expect(options.rawValue == expected)
|
||||
}
|
||||
|
||||
@Test func testInitWithUInt32RawValue() {
|
||||
let raw = UInt32(SQLITE_PREPARE_PERSISTENT)
|
||||
let options = Statement.Options(rawValue: raw)
|
||||
#expect(options == .persistent)
|
||||
}
|
||||
|
||||
@Test func testInitWithInt32RawValue() {
|
||||
let raw = Int32(SQLITE_PREPARE_NO_VTAB)
|
||||
let options = Statement.Options(rawValue: raw)
|
||||
#expect(options == .noVtab)
|
||||
}
|
||||
|
||||
@Test func testEmptySetRawValueIsZero() {
|
||||
let empty: Statement.Options = []
|
||||
#expect(empty.rawValue == 0)
|
||||
#expect(!empty.contains(.persistent))
|
||||
#expect(!empty.contains(.noVtab))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +1,240 @@
|
||||
import XCTest
|
||||
import Foundation
|
||||
import Testing
|
||||
import DataLiteC
|
||||
|
||||
@testable import DataLiteCore
|
||||
|
||||
final class StatementTests: XCTestCase {
|
||||
private let databasePath = FileManager.default.temporaryDirectory.appendingPathComponent("test.db").path
|
||||
private var connection: OpaquePointer!
|
||||
final class StatementTests {
|
||||
let connection: OpaquePointer
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
|
||||
XCTAssertEqual(
|
||||
sqlite3_open(databasePath, &connection),
|
||||
SQLITE_OK,
|
||||
"Failed to open database"
|
||||
init() {
|
||||
var connection: OpaquePointer! = nil
|
||||
let opts = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
|
||||
sqlite3_open_v2(":memory:", &connection, opts, nil)
|
||||
sqlite3_exec(
|
||||
connection,
|
||||
"""
|
||||
CREATE TABLE t(
|
||||
id INTEGER PRIMARY KEY,
|
||||
n INTEGER,
|
||||
r REAL,
|
||||
s TEXT,
|
||||
b BLOB
|
||||
);
|
||||
""", nil, nil, nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
sqlite3_exec(
|
||||
connection,
|
||||
"""
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
age INTEGER
|
||||
);
|
||||
""",
|
||||
nil, nil, nil
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
deinit {
|
||||
sqlite3_close_v2(connection)
|
||||
}
|
||||
|
||||
@Test func testInitWithError() throws {
|
||||
#expect(
|
||||
throws: SQLiteError(
|
||||
code: SQLITE_ERROR,
|
||||
message: "no such table: invalid"
|
||||
),
|
||||
SQLITE_OK,
|
||||
"Failed to create table"
|
||||
performing: {
|
||||
try Statement(
|
||||
db: connection,
|
||||
sql: "SELECT * FROM invalid",
|
||||
options: []
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
sqlite3_close(connection)
|
||||
try FileManager.default.removeItem(atPath: databasePath)
|
||||
try super.tearDownWithError()
|
||||
@Test func testParameterCount() throws {
|
||||
let sql = "SELECT * FROM t WHERE id = ? AND s = ?"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.parameterCount() == 2)
|
||||
}
|
||||
|
||||
func testMixBindings() throws {
|
||||
do {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
try stmt.bind("Alice", at: 1)
|
||||
try stmt.bind(88, at: 2)
|
||||
XCTAssertFalse(try stmt.step())
|
||||
}
|
||||
|
||||
do {
|
||||
let sql = "SELECT * FROM users WHERE age = ? AND name = $name"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
try stmt.bind(88, at: 1)
|
||||
try stmt.bind("Alice", at: stmt.bind(parameterIndexBy: "$name"))
|
||||
XCTAssertTrue(try stmt.step())
|
||||
XCTAssertEqual(stmt.columnValue(at: 1), "Alice")
|
||||
XCTAssertEqual(stmt.columnValue(at: 2), 88)
|
||||
}
|
||||
@Test func testZeroParameterCount() throws {
|
||||
let sql = "SELECT * FROM t"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.parameterCount() == 0)
|
||||
}
|
||||
|
||||
func testStatementInitialization() throws {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let statement = try Statement(db: connection, sql: sql, options: [.persistent])
|
||||
XCTAssertNotNil(statement, "Statement should not be nil")
|
||||
@Test func testParameterIndexByName() throws {
|
||||
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.parameterIndexBy(":id") == 1)
|
||||
#expect(stmt.parameterIndexBy(":s") == 2)
|
||||
#expect(stmt.parameterIndexBy(":invalid") == 0)
|
||||
}
|
||||
|
||||
func testBindAndExecute() throws {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let statement = try Statement(db: connection, sql: sql, options: [.persistent])
|
||||
try statement.bind("Alice", at: 1)
|
||||
try statement.bind(30, at: 2)
|
||||
XCTAssertEqual(statement.bindParameterCount(), 2)
|
||||
XCTAssertFalse(try statement.step())
|
||||
|
||||
let query = "SELECT * FROM users WHERE name = ?"
|
||||
let queryStatement = try Statement(db: connection, sql: query, options: [.persistent])
|
||||
try queryStatement.bind("Alice", at: 1)
|
||||
|
||||
XCTAssertTrue(try queryStatement.step(), "Failed to execute SELECT query")
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 1), "Alice")
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 2), 30)
|
||||
@Test func testParameterNameByIndex() throws {
|
||||
let sql = "SELECT * FROM t WHERE id = :id AND s = :s"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.parameterNameBy(1) == ":id")
|
||||
#expect(stmt.parameterNameBy(2) == ":s")
|
||||
#expect(stmt.parameterNameBy(3) == nil)
|
||||
}
|
||||
|
||||
func testClearBindings() throws {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let statement = try Statement(db: connection, sql: sql, options: [.persistent])
|
||||
try statement.bind("Bob", at: 1)
|
||||
try statement.bind(25, at: 2)
|
||||
try statement.clearBindings()
|
||||
XCTAssertFalse(try statement.step())
|
||||
@Test func testBindValueAtIndex() throws {
|
||||
let sql = "SELECT * FROM t where id = ?"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
try stmt.bind(.int(42), at: 1)
|
||||
try stmt.bind(.real(42), at: 1)
|
||||
try stmt.bind(.text("42"), at: 1)
|
||||
try stmt.bind(.blob(Data([0x42])), at: 1)
|
||||
try stmt.bind(.null, at: 1)
|
||||
try stmt.bind(TestValue(value: 42), at: 1)
|
||||
try stmt.bind(TestValue?.none, at: 1)
|
||||
}
|
||||
|
||||
func testResetStatement() throws {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let statement = try Statement(db: connection, sql: sql, options: [.persistent])
|
||||
try statement.bind("Charlie", at: 1)
|
||||
try statement.bind(40, at: 2)
|
||||
try statement.step()
|
||||
|
||||
// Reset the statement and try executing it again with new values
|
||||
try statement.reset()
|
||||
try statement.bind("Dave", at: 1)
|
||||
try statement.bind(45, at: 2)
|
||||
XCTAssertEqual(statement.bindParameterCount(), 2)
|
||||
XCTAssertFalse(try statement.step())
|
||||
|
||||
// Check if the record was actually inserted
|
||||
let query = "SELECT * FROM users WHERE name = ?"
|
||||
let queryStatement = try Statement(db: connection, sql: query, options: [.persistent])
|
||||
try queryStatement.bind("Dave", at: 1)
|
||||
|
||||
XCTAssertTrue(try queryStatement.step(), "Failed to execute SELECT query")
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 1), "Dave")
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 2), 45)
|
||||
@Test func testErrorBindValueAtIndex() throws {
|
||||
let sql = "SELECT * FROM t where id = ?"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(
|
||||
throws: SQLiteError(
|
||||
code: SQLITE_RANGE,
|
||||
message: "column index out of range"
|
||||
),
|
||||
performing: {
|
||||
try stmt.bind(.null, at: 0)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func testColumnValues() throws {
|
||||
let sql = "INSERT INTO users (name, age) VALUES (?, ?)"
|
||||
let statement = try Statement(db: connection, sql: sql, options: [.persistent])
|
||||
try statement.bind("Eve", at: 1)
|
||||
try statement.bind(28, at: 2)
|
||||
try statement.step()
|
||||
@Test func testBindValueByName() throws {
|
||||
let sql = "SELECT * FROM t where id = :id"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
try stmt.bind(.int(42), by: ":id")
|
||||
try stmt.bind(.real(42), by: ":id")
|
||||
try stmt.bind(.text("42"), by: ":id")
|
||||
try stmt.bind(.blob(Data([0x42])), by: ":id")
|
||||
try stmt.bind(.null, by: ":id")
|
||||
try stmt.bind(TestValue(value: 42), by: ":id")
|
||||
try stmt.bind(TestValue?.none, by: ":id")
|
||||
}
|
||||
|
||||
@Test func testErrorBindValueByName() throws {
|
||||
let sql = "SELECT * FROM t where id = :id"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(
|
||||
throws: SQLiteError(
|
||||
code: SQLITE_RANGE,
|
||||
message: "column index out of range"
|
||||
),
|
||||
performing: {
|
||||
try stmt.bind(.null, by: ":invalid")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test func testStepOneRow() throws {
|
||||
let sql = "SELECT 1 where 1"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(try stmt.step())
|
||||
#expect(try stmt.step() == false)
|
||||
}
|
||||
|
||||
@Test func testStepMultipleRows() throws {
|
||||
sqlite3_exec(connection, "INSERT INTO t(n) VALUES (1),(2),(3)", nil, nil, nil)
|
||||
let sql = "SELECT id FROM t ORDER BY id"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(try stmt.step())
|
||||
#expect(try stmt.step())
|
||||
#expect(try stmt.step())
|
||||
#expect(try stmt.step() == false)
|
||||
}
|
||||
|
||||
@Test func testStepNoRows() throws {
|
||||
let sql = "SELECT 1 WHERE 0"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(try stmt.step() == false)
|
||||
}
|
||||
|
||||
@Test func testStepWithError() throws {
|
||||
sqlite3_exec(connection, "INSERT INTO t(id, n) VALUES (1, 10)", nil, nil, nil)
|
||||
let sql = "INSERT INTO t(id, n) VALUES (?, ?)"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
try stmt.bind(.int(1), at: 1)
|
||||
try stmt.bind(.int(20), at: 2)
|
||||
#expect(
|
||||
throws: SQLiteError(
|
||||
code: 1555,
|
||||
message: "UNIQUE constraint failed: t.id"
|
||||
),
|
||||
performing: {
|
||||
try stmt.step()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test func testColumnCount() throws {
|
||||
let sql = "SELECT * FROM t"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.columnCount() == 5)
|
||||
}
|
||||
|
||||
@Test func testColumnName() throws {
|
||||
let sql = "SELECT * FROM t"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
#expect(stmt.columnName(at: 0) == "id")
|
||||
#expect(stmt.columnName(at: 1) == "n")
|
||||
#expect(stmt.columnName(at: 2) == "r")
|
||||
#expect(stmt.columnName(at: 3) == "s")
|
||||
#expect(stmt.columnName(at: 4) == "b")
|
||||
}
|
||||
|
||||
@Test func testColumnValueAtIndex() throws {
|
||||
sqlite3_exec(connection, """
|
||||
INSERT INTO t (id, n, r, s, b)
|
||||
VALUES (10, 42, 3.5, 'hello', x'DEADBEEF')
|
||||
""", nil, nil, nil
|
||||
)
|
||||
|
||||
// Perform a SELECT query and check column data types
|
||||
let query = "SELECT * FROM users WHERE name = ?"
|
||||
let queryStatement = try Statement(db: connection, sql: query, options: [.persistent])
|
||||
try queryStatement.bind("Eve", at: 1)
|
||||
let sql = "SELECT * FROM t WHERE id = 10"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
|
||||
XCTAssertTrue(try queryStatement.step(), "Failed to execute SELECT query")
|
||||
XCTAssertEqual(queryStatement.columnType(at: 1), .text)
|
||||
XCTAssertEqual(queryStatement.columnType(at: 2), .int)
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 1), "Eve")
|
||||
XCTAssertEqual(queryStatement.columnValue(at: 2), 28)
|
||||
#expect(try stmt.step())
|
||||
#expect(stmt.columnValue(at: 0) == .int(10))
|
||||
#expect(stmt.columnValue(at: 1) == .int(42))
|
||||
#expect(stmt.columnValue(at: 1) == TestValue(value: 42))
|
||||
#expect(stmt.columnValue(at: 2) == .real(3.5))
|
||||
#expect(stmt.columnValue(at: 3) == .text("hello"))
|
||||
#expect(stmt.columnValue(at: 4) == .blob(Data([0xDE, 0xAD, 0xBE, 0xEF])))
|
||||
}
|
||||
|
||||
@Test func testColumnNullValueAtIndex() throws {
|
||||
sqlite3_exec(connection, """
|
||||
INSERT INTO t (id) VALUES (10)
|
||||
""", nil, nil, nil
|
||||
)
|
||||
|
||||
let sql = "SELECT * FROM t WHERE id = 10"
|
||||
let stmt = try Statement(db: connection, sql: sql, options: [])
|
||||
|
||||
#expect(try stmt.step())
|
||||
#expect(stmt.columnValue(at: 0) == .int(10))
|
||||
#expect(stmt.columnValue(at: 1) == .null)
|
||||
#expect(stmt.columnValue(at: 1) == TestValue?.none)
|
||||
}
|
||||
}
|
||||
|
||||
private extension StatementTests {
|
||||
struct TestValue: SQLiteRepresentable, Equatable {
|
||||
let value: Int
|
||||
|
||||
var sqliteValue: SQLiteValue {
|
||||
.int(Int64(value))
|
||||
}
|
||||
|
||||
init(value: Int) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
init?(_ value: SQLiteValue) {
|
||||
if case .int(let intValue) = value {
|
||||
self.value = Int(intValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user