DataLiteCore swift package

This commit is contained in:
2025-04-24 23:48:46 +03:00
parent b0e52a72b7
commit 6f955b2c43
70 changed files with 7939 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
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")
}
}

View File

@@ -0,0 +1,25 @@
import Testing
import Foundation
import DataLiteCore
struct ConnectionKeyTests {
@Test func testPassphrase() {
let key = Connection.Key.passphrase("secret123")
#expect(key.keyValue == "secret123")
#expect(key.length == 9)
}
@Test func testRawKey() {
let keyData = Data([0x01, 0xAB, 0xCD, 0xEF])
let key = Connection.Key.rawKey(keyData)
#expect(key.keyValue == "X'01ABCDEF'")
#expect(key.length == 11)
}
@Test func testRawKeyLengthConsistency() {
let rawBytes = Data(repeating: 0x00, count: 32)
let key = Connection.Key.rawKey(rawBytes)
let hexPart = key.keyValue.dropFirst(2).dropLast()
#expect(hexPart.count == 64)
}
}

View File

@@ -0,0 +1,31 @@
import Testing
@testable import DataLiteCore
struct ConnectionLocationTests {
@Test func testFileLocationPath() {
let filePath = "/path/to/database.db"
let location = Connection.Location.file(path: filePath)
#expect(location.path == filePath)
}
@Test func testInMemoryLocationPath() {
let inMemoryLocation = Connection.Location.inMemory
#expect(inMemoryLocation.path == ":memory:")
}
@Test func testTemporaryLocationPath() {
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)")
}
}
}

View File

@@ -0,0 +1,69 @@
import Testing
import DataLiteC
import DataLiteCore
struct ConnectionOptionsTests {
@Test func testReadOnlyOption() {
let options: Connection.Options = [.readonly]
#expect(options.contains(.readonly))
}
@Test func testReadWriteOption() {
let options: Connection.Options = [.readwrite]
#expect(options.contains(.readwrite))
}
@Test func testCreateOption() {
let options: Connection.Options = [.create]
#expect(options.contains(.create))
}
@Test func testMultipleOptions() {
let options: Connection.Options = [.readwrite, .create, .memory]
#expect(options.contains(.readwrite))
#expect(options.contains(.create))
#expect(options.contains(.memory))
}
@Test func testNoFollowOption() {
let options: Connection.Options = [.nofollow]
#expect(options.contains(.nofollow))
}
@Test func testAllOptions() {
let options: Connection.Options = [
.readonly, .readwrite, .create, .uri, .memory,
.nomutex, .fullmutex, .sharedcache,
.privatecache, .exrescode, .nofollow
]
#expect(options.contains(.readonly))
#expect(options.contains(.readwrite))
#expect(options.contains(.create))
#expect(options.contains(.uri))
#expect(options.contains(.memory))
#expect(options.contains(.nomutex))
#expect(options.contains(.fullmutex))
#expect(options.contains(.sharedcache))
#expect(options.contains(.privatecache))
#expect(options.contains(.exrescode))
#expect(options.contains(.nofollow))
}
@Test func testOptionsRawValue() {
let options: Connection.Options = [.readwrite, .create]
let expectedRawValue = Int32(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
#expect(options.rawValue == expectedRawValue)
#expect(Connection.Options.readonly.rawValue == SQLITE_OPEN_READONLY)
#expect(Connection.Options.readwrite.rawValue == SQLITE_OPEN_READWRITE)
#expect(Connection.Options.create.rawValue == SQLITE_OPEN_CREATE)
#expect(Connection.Options.memory.rawValue == SQLITE_OPEN_MEMORY)
#expect(Connection.Options.nomutex.rawValue == SQLITE_OPEN_NOMUTEX)
#expect(Connection.Options.fullmutex.rawValue == SQLITE_OPEN_FULLMUTEX)
#expect(Connection.Options.sharedcache.rawValue == SQLITE_OPEN_SHAREDCACHE)
#expect(Connection.Options.privatecache.rawValue == SQLITE_OPEN_PRIVATECACHE)
#expect(Connection.Options.exrescode.rawValue == SQLITE_OPEN_EXRESCODE)
#expect(Connection.Options.nofollow.rawValue == SQLITE_OPEN_NOFOLLOW)
}
}

View File

@@ -0,0 +1,277 @@
import Foundation
import Testing
import DataLiteC
import DataLiteCore
struct ConnectionTests {
@Test func testIsAutocommitInitially() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
#expect(connection.isAutocommit == true)
}
@Test func testIsAutocommitDuringTransaction() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
try connection.beginTransaction()
#expect(connection.isAutocommit == false)
}
@Test func testIsAutocommitAfterCommit() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
try connection.beginTransaction()
try connection.commitTransaction()
#expect(connection.isAutocommit == true)
}
@Test func testIsAutocommitAfterRollback() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
try connection.beginTransaction()
try connection.rollbackTransaction()
#expect(connection.isAutocommit == true)
}
@Test(arguments: [
(Connection.Options.readwrite, false),
(Connection.Options.readonly, true)
])
func testIsReadonly(
_ opt: Connection.Options,
_ isReadonly: Bool
) throws {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("sqlite")
defer { try? FileManager.default.removeItem(at: url) }
let _ = try Connection(
location: .file(path: url.path),
options: [.create, .readwrite]
)
let connection = try Connection(
location: .file(path: url.path),
options: [opt]
)
#expect(connection.isReadonly == isReadonly)
}
@Test func testBusyTimeout() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
connection.busyTimeout = 5000
#expect(try connection.get(pragma: .busyTimeout) == 5000)
try connection.set(pragma: .busyTimeout, value: 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(raw: """
CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT);
""")
try oneConn.beginTransaction()
try oneConn.execute(raw: """
INSERT INTO test (value) VALUES ('first');
""")
#expect(
throws: Connection.Error(
code: SQLITE_BUSY,
message: "database is locked"
),
performing: {
twoConn.busyTimeout = 0
try twoConn.execute(raw: """
INSERT INTO test (value) VALUES ('second');
""")
}
)
try oneConn.rollbackTransaction()
}
@Test func testApplicationID() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
#expect(connection.applicationID == 0)
connection.applicationID = 1024
#expect(try connection.get(pragma: .applicationID) == 1024)
try connection.set(pragma: .applicationID, value: 123)
#expect(connection.applicationID == 123)
}
@Test func testForeignKeys() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
#expect(connection.foreignKeys == false)
connection.foreignKeys = true
#expect(try connection.get(pragma: .foreignKeys) == true)
try connection.set(pragma: .foreignKeys, value: false)
#expect(connection.foreignKeys == false)
}
@Test func testJournalMode() throws {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("sqlite")
defer { try? FileManager.default.removeItem(at: url) }
let connection = try Connection(
location: .file(path: url.path),
options: [.create, .readwrite]
)
connection.journalMode = .delete
#expect(try connection.get(pragma: .journalMode) == JournalMode.delete)
try connection.set(pragma: .journalMode, value: JournalMode.wal)
#expect(connection.journalMode == .wal)
}
@Test func testSynchronous() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
connection.synchronous = .normal
#expect(try connection.get(pragma: .synchronous) == Synchronous.normal)
try connection.set(pragma: .synchronous, value: Synchronous.full)
#expect(connection.synchronous == .full)
}
@Test func testUserVersion() throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
connection.userVersion = 42
#expect(try connection.get(pragma: .userVersion) == 42)
try connection.set(pragma: .userVersion, value: 13)
#expect(connection.userVersion == 13)
}
@Test(arguments: [
(TestScalarFunc.self, TestScalarFunc.name),
(TestAggregateFunc.self, TestAggregateFunc.name)
] as [(Function.Type, String)])
func testAddFunction(
_ function: Function.Type,
_ name: String
) throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
try connection.execute(sql: """
CREATE TABLE items (value INTEGER);
INSERT INTO items (value) VALUES (1), (2), (NULL), (3);
""")
try connection.add(function: function)
try connection.execute(raw: "SELECT \(name)(value) FROM items")
}
@Test(arguments: [
(TestScalarFunc.self, TestScalarFunc.name),
(TestAggregateFunc.self, TestAggregateFunc.name)
] as [(Function.Type, String)])
func testRemoveFunction(
_ function: Function.Type,
_ name: String
) throws {
let connection = try Connection(
location: .inMemory,
options: [.create, .readwrite]
)
try connection.execute(sql: """
CREATE TABLE items (value INTEGER);
INSERT INTO items (value) VALUES (1), (2), (NULL), (3);
""")
try connection.add(function: function)
try connection.remove(function: function)
#expect(
throws: Connection.Error(
code: SQLITE_ERROR,
message: "no such function: \(name)"
),
performing: {
try connection.execute(raw: """
SELECT \(name)(value) FROM items
""")
}
)
}
}
private extension ConnectionTests {
final class TestScalarFunc: Function.Scalar {
override class var argc: Int32 { 1 }
override class var name: String { "TO_STR" }
override class var options: Options {
[.deterministic, .innocuous]
}
override class func invoke(args: Arguments) throws -> SQLiteRawRepresentable? {
args[0].description
}
}
final class TestAggregateFunc: Function.Aggregate {
override class var argc: Int32 { 1 }
override class var name: String { "MY_COUNT" }
override class var options: Options {
[.deterministic, .innocuous]
}
private var count: Int = 0
override func step(args: Arguments) throws {
if args[0] != .null {
count += 1
}
}
override func finalize() throws -> SQLiteRawRepresentable? {
count
}
}
}

View File

@@ -0,0 +1,58 @@
import Testing
import DataLiteC
import DataLiteCore
struct FunctionOptionsTests {
@Test func testSingleOption() {
#expect(Function.Options.deterministic.rawValue == SQLITE_DETERMINISTIC)
#expect(Function.Options.directonly.rawValue == SQLITE_DIRECTONLY)
#expect(Function.Options.innocuous.rawValue == SQLITE_INNOCUOUS)
}
@Test func testMultipleOptions() {
let options: Function.Options = [.deterministic, .directonly]
#expect(options.contains(.deterministic))
#expect(options.contains(.directonly))
#expect(options.contains(.innocuous) == false)
}
@Test func testEqualityAndHashability() {
let options1: Function.Options = [.deterministic, .innocuous]
let options2: Function.Options = [.deterministic, .innocuous]
#expect(options1 == options2)
let hash1 = options1.hashValue
let hash2 = options2.hashValue
#expect(hash1 == hash2)
}
@Test func testEmptyOptions() {
let options = Function.Options(rawValue: 0)
#expect(options.contains(.deterministic) == false)
#expect(options.contains(.directonly) == false)
#expect(options.contains(.innocuous) == false)
}
@Test func testRawValueInitialization() {
let rawValue: Int32 = SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS
let options = Function.Options(rawValue: rawValue)
#expect(options.contains(.deterministic))
#expect(options.contains(.innocuous))
#expect(options.contains(.directonly) == false)
}
@Test func testAddingAndRemovingOptions() {
var options: Function.Options = []
options.insert(.deterministic)
#expect(options.contains(.deterministic))
options.insert(.directonly)
#expect(options.contains(.directonly))
options.remove(.deterministic)
#expect(options.contains(.deterministic) == false)
}
}

View File

@@ -0,0 +1,45 @@
import Testing
import DataLiteCore
import DataLiteC
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)
#expect(Statement.Options.persistent.rawValue == UInt32(SQLITE_PREPARE_PERSISTENT))
#expect(Statement.Options.noVtab.rawValue == UInt32(SQLITE_PREPARE_NO_VTAB))
}
}

View File

@@ -0,0 +1,135 @@
import XCTest
import DataLiteC
@testable import DataLiteCore
final class StatementTests: XCTestCase {
private let databasePath = FileManager.default.temporaryDirectory.appendingPathComponent("test.db").path
private var connection: OpaquePointer!
override func setUpWithError() throws {
try super.setUpWithError()
XCTAssertEqual(
sqlite3_open(databasePath, &connection),
SQLITE_OK,
"Failed to open database"
)
XCTAssertEqual(
sqlite3_exec(
connection,
"""
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER
);
""",
nil, nil, nil
),
SQLITE_OK,
"Failed to create table"
)
}
override func tearDownWithError() throws {
sqlite3_close(connection)
try FileManager.default.removeItem(atPath: databasePath)
try super.tearDownWithError()
}
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)
}
}
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")
}
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)
}
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())
}
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)
}
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()
// 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)
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)
}
}