DataLiteCore swift package
This commit is contained in:
28
Tests/DataLiteCoreTests/Classes/Connection+ErrorTests.swift
Normal file
28
Tests/DataLiteCoreTests/Classes/Connection+ErrorTests.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
25
Tests/DataLiteCoreTests/Classes/Connection+KeyTests.swift
Normal file
25
Tests/DataLiteCoreTests/Classes/Connection+KeyTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
277
Tests/DataLiteCoreTests/Classes/ConnectionTests.swift
Normal file
277
Tests/DataLiteCoreTests/Classes/ConnectionTests.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Tests/DataLiteCoreTests/Classes/Function+OptionsTests.swift
Normal file
58
Tests/DataLiteCoreTests/Classes/Function+OptionsTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
45
Tests/DataLiteCoreTests/Classes/Statement+OptionsTests.swift
Normal file
45
Tests/DataLiteCoreTests/Classes/Statement+OptionsTests.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
135
Tests/DataLiteCoreTests/Classes/StatementTests.swift
Normal file
135
Tests/DataLiteCoreTests/Classes/StatementTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
43
Tests/DataLiteCoreTests/Enums/SQLiteActionTests.swift
Normal file
43
Tests/DataLiteCoreTests/Enums/SQLiteActionTests.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Tests/DataLiteCoreTests/Enums/SQLiteRawTypeTests.swift
Normal file
27
Tests/DataLiteCoreTests/Enums/SQLiteRawTypeTests.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import Testing
|
||||
import DataLiteC
|
||||
import DataLiteCore
|
||||
|
||||
struct SQLiteRawTypeTests {
|
||||
@Test func testInitializationFromRawValue() {
|
||||
#expect(SQLiteRawType(rawValue: SQLITE_INTEGER) == .int)
|
||||
#expect(SQLiteRawType(rawValue: SQLITE_FLOAT) == .real)
|
||||
#expect(SQLiteRawType(rawValue: SQLITE_TEXT) == .text)
|
||||
#expect(SQLiteRawType(rawValue: SQLITE_BLOB) == .blob)
|
||||
#expect(SQLiteRawType(rawValue: SQLITE_NULL) == .null)
|
||||
#expect(SQLiteRawType(rawValue: -1) == nil)
|
||||
}
|
||||
|
||||
@Test func testRawValue() {
|
||||
#expect(SQLiteRawType.int.rawValue == SQLITE_INTEGER)
|
||||
#expect(SQLiteRawType.real.rawValue == SQLITE_FLOAT)
|
||||
#expect(SQLiteRawType.text.rawValue == SQLITE_TEXT)
|
||||
#expect(SQLiteRawType.blob.rawValue == SQLITE_BLOB)
|
||||
#expect(SQLiteRawType.null.rawValue == SQLITE_NULL)
|
||||
}
|
||||
|
||||
@Test func testInvalidRawValue() {
|
||||
let invalidRawValue: Int32 = 9999
|
||||
#expect(SQLiteRawType(rawValue: invalidRawValue) == nil)
|
||||
}
|
||||
}
|
||||
36
Tests/DataLiteCoreTests/Enums/SQLiteRawValueTests.swift
Normal file
36
Tests/DataLiteCoreTests/Enums/SQLiteRawValueTests.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct SQLiteRawValueTests {
|
||||
@Test func testIntValue() {
|
||||
let value = SQLiteRawValue.int(42)
|
||||
#expect(value.description == "42")
|
||||
}
|
||||
|
||||
@Test func testRealValue() {
|
||||
let value = SQLiteRawValue.real(3.14)
|
||||
#expect(value.description == "3.14")
|
||||
}
|
||||
|
||||
@Test func testTextValue() {
|
||||
let value = SQLiteRawValue.text("Hello, World!")
|
||||
#expect(value.description == "'Hello, World!'")
|
||||
}
|
||||
|
||||
@Test func testTextValueWithSingleQuote() {
|
||||
let value = SQLiteRawValue.text("O'Reilly")
|
||||
#expect(value.description == "'O''Reilly'") // Escaped single quote
|
||||
}
|
||||
|
||||
@Test func testBlobValue() {
|
||||
let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
|
||||
let value = SQLiteRawValue.blob(data)
|
||||
#expect(value.description == "X'DEADBEEF'")
|
||||
}
|
||||
|
||||
@Test func testNullValue() {
|
||||
let value = SQLiteRawValue.null
|
||||
#expect(value.description == "NULL")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import Testing
|
||||
import DataLiteCore
|
||||
|
||||
struct BinaryFloatingPointTests {
|
||||
@Test func testFloatToSQLiteRawValue() {
|
||||
let floatValue: Float = 3.14
|
||||
let rawValue = floatValue.sqliteRawValue
|
||||
#expect(rawValue == .real(Double(floatValue)))
|
||||
}
|
||||
|
||||
@Test func testDoubleToSQLiteRawValue() {
|
||||
let doubleValue: Double = 3.14
|
||||
let rawValue = doubleValue.sqliteRawValue
|
||||
#expect(rawValue == .real(doubleValue))
|
||||
}
|
||||
|
||||
@Test func testFloatInitializationFromSQLiteRawValue() {
|
||||
let realValue: SQLiteRawValue = .real(3.14)
|
||||
let floatValue = Float(realValue)
|
||||
#expect(floatValue != nil)
|
||||
#expect(floatValue == 3.14)
|
||||
|
||||
let intValue: SQLiteRawValue = .int(42)
|
||||
let floatFromInt = Float(intValue)
|
||||
#expect(floatFromInt != nil)
|
||||
#expect(floatFromInt == 42.0)
|
||||
}
|
||||
|
||||
@Test func testDoubleInitializationFromSQLiteRawValue() {
|
||||
let realValue: SQLiteRawValue = .real(3.14)
|
||||
let doubleValue = Double(realValue)
|
||||
#expect(doubleValue != nil)
|
||||
#expect(doubleValue == 3.14)
|
||||
|
||||
let intValue: SQLiteRawValue = .int(42)
|
||||
let doubleFromInt = Double(intValue)
|
||||
#expect(doubleFromInt != nil)
|
||||
#expect(doubleFromInt == 42.0)
|
||||
}
|
||||
|
||||
@Test func testInitializationFailureFromInvalidSQLiteRawValue() {
|
||||
let nullValue: SQLiteRawValue = .null
|
||||
#expect(Float(nullValue) == nil)
|
||||
#expect(Double(nullValue) == nil)
|
||||
|
||||
let textValue: SQLiteRawValue = .text("Invalid")
|
||||
#expect(Float(textValue) == nil)
|
||||
#expect(Double(textValue) == nil)
|
||||
}
|
||||
}
|
||||
43
Tests/DataLiteCoreTests/Extensions/BinaryIntegerTests.swift
Normal file
43
Tests/DataLiteCoreTests/Extensions/BinaryIntegerTests.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct BinaryIntegerTests {
|
||||
@Test func testIntegerToSQLiteRawValue() {
|
||||
#expect(Int(42).sqliteRawValue == .int(42))
|
||||
#expect(Int8(42).sqliteRawValue == .int(42))
|
||||
#expect(Int16(42).sqliteRawValue == .int(42))
|
||||
#expect(Int32(42).sqliteRawValue == .int(42))
|
||||
#expect(Int64(42).sqliteRawValue == .int(42))
|
||||
|
||||
#expect(UInt(42).sqliteRawValue == .int(42))
|
||||
#expect(UInt8(42).sqliteRawValue == .int(42))
|
||||
#expect(UInt16(42).sqliteRawValue == .int(42))
|
||||
#expect(UInt32(42).sqliteRawValue == .int(42))
|
||||
#expect(UInt64(42).sqliteRawValue == .int(42))
|
||||
}
|
||||
|
||||
@Test func testIntegerInitializationFromSQLiteRawValue() {
|
||||
#expect(Int(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(Int8(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(Int16(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(Int32(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(Int64(SQLiteRawValue.int(42)) == 42)
|
||||
|
||||
#expect(UInt(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(UInt8(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(UInt16(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(UInt32(SQLiteRawValue.int(42)) == 42)
|
||||
#expect(UInt64(SQLiteRawValue.int(42)) == 42)
|
||||
}
|
||||
|
||||
@Test func testInvalidIntegerInitialization() {
|
||||
#expect(Int(SQLiteRawValue.real(3.14)) == nil)
|
||||
#expect(Int8(SQLiteRawValue.text("test")) == nil)
|
||||
#expect(UInt32(SQLiteRawValue.blob(Data([0x01, 0x02]))) == nil)
|
||||
|
||||
// Out-of-range conversion
|
||||
let largeValue = Int64.max
|
||||
#expect(Int8(exactly: largeValue) == nil)
|
||||
}
|
||||
}
|
||||
22
Tests/DataLiteCoreTests/Extensions/BoolTests.swift
Normal file
22
Tests/DataLiteCoreTests/Extensions/BoolTests.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct BoolTests {
|
||||
@Test func testBoolToSQLiteRawValue() {
|
||||
#expect(true.sqliteRawValue == .int(1))
|
||||
#expect(false.sqliteRawValue == .int(0))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToBool() {
|
||||
#expect(Bool(.int(1)) == true)
|
||||
#expect(Bool(.int(0)) == false)
|
||||
|
||||
#expect(Bool(.int(-1)) == nil)
|
||||
#expect(Bool(.int(2)) == nil)
|
||||
#expect(Bool(.real(1.0)) == nil)
|
||||
#expect(Bool(.text("true")) == nil)
|
||||
#expect(Bool(.blob(Data())) == nil)
|
||||
#expect(Bool(.null) == nil)
|
||||
}
|
||||
}
|
||||
22
Tests/DataLiteCoreTests/Extensions/DataTests.swift
Normal file
22
Tests/DataLiteCoreTests/Extensions/DataTests.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct DataSQLiteRawRepresentableTests {
|
||||
@Test func testDataToSQLiteRawValue() {
|
||||
let data = Data([0x01, 0x02, 0x03])
|
||||
#expect(data.sqliteRawValue == .blob(data))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToData() {
|
||||
let data = Data([0x01, 0x02, 0x03])
|
||||
let rawValue = SQLiteRawValue.blob(data)
|
||||
|
||||
#expect(Data(rawValue) == data)
|
||||
|
||||
#expect(Data(.int(1)) == nil)
|
||||
#expect(Data(.real(1.0)) == nil)
|
||||
#expect(Data(.text("blob")) == nil)
|
||||
#expect(Data(.null) == nil)
|
||||
}
|
||||
}
|
||||
32
Tests/DataLiteCoreTests/Extensions/DateTests.swift
Normal file
32
Tests/DataLiteCoreTests/Extensions/DateTests.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct DateSQLiteRawRepresentableTests {
|
||||
@Test func testDateToSQLiteRawValue() {
|
||||
let date = Date(timeIntervalSince1970: 1609459200)
|
||||
let formatter = ISO8601DateFormatter()
|
||||
let dateString = formatter.string(from: date)
|
||||
|
||||
#expect(date.sqliteRawValue == .text(dateString))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToDate() {
|
||||
let date = Date(timeIntervalSince1970: 1609459200)
|
||||
let formatter = ISO8601DateFormatter()
|
||||
let dateString = formatter.string(from: date)
|
||||
|
||||
let rawText = SQLiteRawValue.text(dateString)
|
||||
#expect(Date(rawText) == date)
|
||||
|
||||
let rawInt = SQLiteRawValue.int(1609459200)
|
||||
#expect(Date(rawInt) == date)
|
||||
|
||||
let rawReal = SQLiteRawValue.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct RawRepresentableTests {
|
||||
@Test func testRawRepresentableToSQLiteRawValue() {
|
||||
let color: Color = .green
|
||||
#expect(color.sqliteRawValue == .int(1))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToRawRepresentable() {
|
||||
#expect(Color(.int(2)) == .blue)
|
||||
|
||||
#expect(Color(.int(42)) == nil)
|
||||
#expect(Color(.text("red")) == nil)
|
||||
#expect(Color(.blob(Data([0x01, 0x02]))) == nil)
|
||||
#expect(Color(.null) == nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension RawRepresentableTests {
|
||||
enum Color: Int, SQLiteRawRepresentable {
|
||||
case red
|
||||
case green
|
||||
case blue
|
||||
}
|
||||
}
|
||||
394
Tests/DataLiteCoreTests/Extensions/String+SQLTests.swift
Normal file
394
Tests/DataLiteCoreTests/Extensions/String+SQLTests.swift
Normal file
@@ -0,0 +1,394 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import DataLiteCore
|
||||
|
||||
struct StringSQLTests {
|
||||
// MARK: - Test Remove Single Line Comments
|
||||
|
||||
@Test func testSingleLineCommentAtStart() {
|
||||
let input = """
|
||||
-- This is a comment
|
||||
SELECT * FROM users;
|
||||
"""
|
||||
let expected = """
|
||||
|
||||
SELECT * FROM users;
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testSingleLineCommentAfterStatement() {
|
||||
let input = """
|
||||
SELECT * FROM users; -- This is a comment
|
||||
"""
|
||||
let expected = """
|
||||
SELECT * FROM users;\u{0020}
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testSingleLineCommentBetweenStatementLines() {
|
||||
let input = """
|
||||
INSERT INTO users (
|
||||
id, name
|
||||
-- comment between statement
|
||||
) VALUES (1, 'Alice');
|
||||
"""
|
||||
let expected = """
|
||||
INSERT INTO users (
|
||||
id, name
|
||||
|
||||
) VALUES (1, 'Alice');
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testSingleLineCommentAtEnd() {
|
||||
let input = """
|
||||
SELECT * FROM users;
|
||||
-- final comment
|
||||
"""
|
||||
let expected = """
|
||||
SELECT * FROM users;
|
||||
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testSingleLineCommentWithTabsAndSpaces() {
|
||||
let input = "SELECT 1;\t -- comment with tab\nSELECT 2;"
|
||||
let expected = "SELECT 1;\t \nSELECT 2;"
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testSingleLineCommentWithLiterals() {
|
||||
let input = """
|
||||
INSERT INTO logs (text) VALUES ('This isn''t -- a comment'); -- trailing comment
|
||||
"""
|
||||
let expected = """
|
||||
INSERT INTO logs (text) VALUES ('This isn''t -- a comment');
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
// MARK: - Test Remove Multiline Comments
|
||||
|
||||
@Test func testMultilineCommentAtStart() {
|
||||
let input = """
|
||||
/* This is a
|
||||
comment at the top */
|
||||
SELECT * FROM users;
|
||||
"""
|
||||
let expected = """
|
||||
|
||||
SELECT * FROM users;
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentAtLineStart() {
|
||||
let input = """
|
||||
/* comment */ SELECT * FROM users;
|
||||
"""
|
||||
let expected = """
|
||||
\u{0020}SELECT * FROM users;
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentInMiddleOfLine() {
|
||||
let input = """
|
||||
SELECT /* inline comment */ * FROM users;
|
||||
"""
|
||||
let expected = """
|
||||
SELECT * FROM users;
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentAtEndOfLine() {
|
||||
let input = """
|
||||
SELECT * FROM users; /* trailing comment */
|
||||
"""
|
||||
let expected = """
|
||||
SELECT * FROM users;\u{0020}
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentBetweenLines() {
|
||||
let input = """
|
||||
INSERT INTO users (
|
||||
id,
|
||||
/* this field stores username */
|
||||
username,
|
||||
email
|
||||
) VALUES (1, 'alice', 'alice@example.com');
|
||||
"""
|
||||
let expected = """
|
||||
INSERT INTO users (
|
||||
id,
|
||||
|
||||
username,
|
||||
email
|
||||
) VALUES (1, 'alice', 'alice@example.com');
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentAtEndOfFile() {
|
||||
let input = """
|
||||
SELECT * FROM users;
|
||||
/* final block comment */
|
||||
"""
|
||||
let expected = """
|
||||
SELECT * FROM users;
|
||||
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
@Test func testMultilineCommentWithLiterals() {
|
||||
let input = """
|
||||
INSERT INTO notes (text) VALUES ('This isn''t /* a comment */ either'); /* trailing comment */
|
||||
"""
|
||||
let expected = """
|
||||
INSERT INTO notes (text) VALUES ('This isn''t /* a comment */ either');\u{0020}
|
||||
"""
|
||||
#expect(input.removingComments() == expected)
|
||||
}
|
||||
|
||||
// MARK: - Test Trimming Lines
|
||||
|
||||
@Test func testTrimmingEmptyFirstLine() {
|
||||
let input = "\nSELECT * FROM users;"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyFirstLineWithSpace() {
|
||||
let input = " \nSELECT * FROM users;"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyFirstLineWithTab() {
|
||||
let input = "\t\nSELECT * FROM users;"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyMiddleLine() {
|
||||
let input = "SELECT *\n\nFROM users;"
|
||||
let expected = "SELECT *\nFROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyMiddleLineWithSpace() {
|
||||
let input = "SELECT *\n\u{0020}\nFROM users;"
|
||||
let expected = "SELECT *\nFROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyMiddleLineWithTab() {
|
||||
let input = "SELECT *\n\t\nFROM users;"
|
||||
let expected = "SELECT *\nFROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyLastLine() {
|
||||
let input = "SELECT * FROM users;\n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyLastLineWithSpace() {
|
||||
let input = "SELECT * FROM users; \n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingEmptyLastLineWithTab() {
|
||||
let input = "SELECT * FROM users;\t\n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingTrailingSpacesOnly() {
|
||||
let input = "SELECT * FROM users; "
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingTrailingSpacesAndNewline() {
|
||||
let input = "SELECT * FROM users; \n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingTrailingTabsOnly() {
|
||||
let input = "SELECT * FROM users;\t\t"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingTrailingTabsAndNewline() {
|
||||
let input = "SELECT * FROM users;\t\t\n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingMultipleEmptyLinesAndSpaces() {
|
||||
let input = "\n\n\t\u{0020}\nSELECT * FROM users;\n\n\u{0020}\n\n"
|
||||
let expected = "SELECT * FROM users;"
|
||||
print("zzzz\n\(input.trimmingLines())")
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingLiteralPreservesWhitespace() {
|
||||
let input = "INSERT INTO logs VALUES ('line with\n\nspaces \t \n\n and tabs');"
|
||||
let expected = input
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
@Test func testTrimmingPreserveLineBreaksInMultilineInsert() {
|
||||
let input = """
|
||||
INSERT INTO users (id, username, email)
|
||||
VALUES \t
|
||||
(1, 'john_doe', 'john@example.com'),
|
||||
(2, 'jane_doe', 'jane@example.com');
|
||||
"""
|
||||
let expected = """
|
||||
INSERT INTO users (id, username, email)
|
||||
VALUES
|
||||
(1, 'john_doe', 'john@example.com'),
|
||||
(2, 'jane_doe', 'jane@example.com');
|
||||
"""
|
||||
#expect(input.trimmingLines() == expected)
|
||||
}
|
||||
|
||||
// MARK: - Test Split Statements
|
||||
|
||||
@Test func testSplitSingleStatement() {
|
||||
let input = "SELECT * FROM users;"
|
||||
let expected = ["SELECT * FROM users"]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitSingleStatementWithoutSemicolon() {
|
||||
let input = "SELECT * FROM users"
|
||||
let expected = ["SELECT * FROM users"]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitMultipleStatements() {
|
||||
let input = """
|
||||
SELECT * FROM users;
|
||||
DELETE FROM users WHERE id=123;
|
||||
DELETE FROM users WHERE id=987;
|
||||
"""
|
||||
let expected = [
|
||||
"SELECT * FROM users",
|
||||
"DELETE FROM users WHERE id=123",
|
||||
"DELETE FROM users WHERE id=987"
|
||||
]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitMultipleStatementsLastWithoutSemicolon() {
|
||||
let input = """
|
||||
SELECT * FROM users;
|
||||
DELETE FROM users WHERE id=1;
|
||||
UPDATE users SET name='Bob' WHERE id=2
|
||||
"""
|
||||
let expected = [
|
||||
"SELECT * FROM users",
|
||||
"DELETE FROM users WHERE id=1",
|
||||
"UPDATE users SET name='Bob' WHERE id=2"
|
||||
]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitTextLiteralSemicolon() {
|
||||
let input = "INSERT INTO logs (msg) VALUES ('Hello; world');"
|
||||
let expected = ["INSERT INTO logs (msg) VALUES ('Hello; world')"]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitTextLiteralEscapingQuotes() {
|
||||
let input = "INSERT INTO test VALUES ('It''s a test');"
|
||||
let expected = ["INSERT INTO test VALUES ('It''s a test')"]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitMultipleSemicolon() {
|
||||
let input = "SELECT * FROM users;;SELECT * FROM users;"
|
||||
let expected = [
|
||||
"SELECT * FROM users",
|
||||
"SELECT * FROM users"
|
||||
]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test(arguments: [
|
||||
("BEGIN", "END"),
|
||||
("Begin", "End"),
|
||||
("begin", "end"),
|
||||
("bEgIn", "eNd"),
|
||||
("BeGiN", "EnD")
|
||||
])
|
||||
func testSplitWithBeginEnd(begin: String, end: String) {
|
||||
let input = """
|
||||
CREATE TABLE KDFMetadata (
|
||||
id INTEGER PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
CREATE TRIGGER KDFMetadataLimit
|
||||
BEFORE INSERT ON KDFMetadata
|
||||
WHEN (SELECT COUNT(*) FROM KDFMetadata) >= 1
|
||||
\(begin)
|
||||
SELECT RAISE(FAIL, 'Only one row allowed in KDFMetadata');
|
||||
\(end);
|
||||
"""
|
||||
let expected = [
|
||||
"""
|
||||
CREATE TABLE KDFMetadata (
|
||||
id INTEGER PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TRIGGER KDFMetadataLimit
|
||||
BEFORE INSERT ON KDFMetadata
|
||||
WHEN (SELECT COUNT(*) FROM KDFMetadata) >= 1
|
||||
\(begin)
|
||||
SELECT RAISE(FAIL, 'Only one row allowed in KDFMetadata');
|
||||
\(end)
|
||||
"""
|
||||
]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
|
||||
@Test func testSplitWithSavepoints() {
|
||||
let input = """
|
||||
SAVEPOINT sp1;
|
||||
INSERT INTO users (id, name) VALUES (1, 'Alice');
|
||||
RELEASE SAVEPOINT sp1;
|
||||
SAVEPOINT sp2;
|
||||
UPDATE users SET name = 'Bob' WHERE id = 1;
|
||||
ROLLBACK TO SAVEPOINT sp2;
|
||||
RELEASE SAVEPOINT sp2;
|
||||
"""
|
||||
let expected = [
|
||||
"SAVEPOINT sp1",
|
||||
"INSERT INTO users (id, name) VALUES (1, 'Alice')",
|
||||
"RELEASE SAVEPOINT sp1",
|
||||
"SAVEPOINT sp2",
|
||||
"UPDATE users SET name = 'Bob' WHERE id = 1",
|
||||
"ROLLBACK TO SAVEPOINT sp2",
|
||||
"RELEASE SAVEPOINT sp2"
|
||||
]
|
||||
#expect(input.splitStatements() == expected)
|
||||
}
|
||||
}
|
||||
17
Tests/DataLiteCoreTests/Extensions/StringTests.swift
Normal file
17
Tests/DataLiteCoreTests/Extensions/StringTests.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct StringTests {
|
||||
@Test func testStringToSQLiteRawValue() {
|
||||
#expect("Hello, SQLite!".sqliteRawValue == .text("Hello, SQLite!"))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToString() {
|
||||
#expect(String(SQLiteRawValue.text("Hello, SQLite!")) == "Hello, SQLite!")
|
||||
|
||||
#expect(String(SQLiteRawValue.int(42)) == nil)
|
||||
#expect(String(SQLiteRawValue.blob(Data([0x01, 0x02]))) == nil)
|
||||
#expect(String(SQLiteRawValue.null) == nil)
|
||||
}
|
||||
}
|
||||
20
Tests/DataLiteCoreTests/Extensions/UUIDTests.swift
Normal file
20
Tests/DataLiteCoreTests/Extensions/UUIDTests.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import DataLiteCore
|
||||
|
||||
struct UUIDTests {
|
||||
@Test func testUUIDToSQLiteRawValue() {
|
||||
let uuid = UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000")!
|
||||
#expect(uuid.sqliteRawValue == .text("123E4567-E89B-12D3-A456-426614174000"))
|
||||
}
|
||||
|
||||
@Test func testSQLiteRawValueToUUID() {
|
||||
let raw = SQLiteRawValue.text("123e4567-e89b-12d3-a456-426614174000")
|
||||
#expect(UUID(raw) == UUID(uuidString: "123e4567-e89b-12d3-a456-426614174000"))
|
||||
|
||||
#expect(UUID(.text("invalid-uuid-string")) == nil)
|
||||
#expect(UUID(.int(42)) == nil)
|
||||
#expect(UUID(.blob(Data([0x01, 0x02]))) == nil)
|
||||
#expect(UUID(.null) == nil)
|
||||
}
|
||||
}
|
||||
1
Tests/DataLiteCoreTests/Resources/empty_script.sql
Normal file
1
Tests/DataLiteCoreTests/Resources/empty_script.sql
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
Tests/DataLiteCoreTests/Resources/invalid_script.sql
Normal file
BIN
Tests/DataLiteCoreTests/Resources/invalid_script.sql
Normal file
Binary file not shown.
16
Tests/DataLiteCoreTests/Resources/valid_script.sql
Normal file
16
Tests/DataLiteCoreTests/Resources/valid_script.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- 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
|
||||
64
Tests/DataLiteCoreTests/Structures/SQLScriptTests.swift
Normal file
64
Tests/DataLiteCoreTests/Structures/SQLScriptTests.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
class SQLScriptTests: XCTestCase {
|
||||
func testInitWithValidFile() throws {
|
||||
let sqlScript = try SQLScript(
|
||||
byResource: "valid_script",
|
||||
extension: "sql",
|
||||
in: .module
|
||||
)
|
||||
XCTAssertNotNil(sqlScript)
|
||||
XCTAssertEqual(sqlScript?.count, 2)
|
||||
}
|
||||
|
||||
func testInitWithEmptyFile() throws {
|
||||
let sqlScript = try SQLScript(
|
||||
byResource: "empty_script",
|
||||
extension: "sql",
|
||||
in: .module
|
||||
)
|
||||
XCTAssertNotNil(sqlScript)
|
||||
XCTAssertEqual(sqlScript?.count, 0)
|
||||
}
|
||||
|
||||
func testInitWithInvalidFile() throws {
|
||||
XCTAssertThrowsError(
|
||||
try SQLScript(
|
||||
byResource: "invalid_script",
|
||||
extension: "sql",
|
||||
in: .module
|
||||
)
|
||||
) { error in
|
||||
let error = error as NSError
|
||||
XCTAssertEqual(error.domain, NSCocoaErrorDomain)
|
||||
XCTAssertEqual(error.code, 259)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccessingStatements() throws {
|
||||
let sqlScript = try SQLScript(
|
||||
byResource: "valid_script",
|
||||
extension: "sql",
|
||||
in: .module
|
||||
)
|
||||
|
||||
let oneStatement = """
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
email TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
let twoStatement = """
|
||||
INSERT INTO users (id, username, email)
|
||||
VALUES
|
||||
(1, 'john_doe', 'john@example.com'),
|
||||
(2, 'jane_doe', 'jane@example.com')
|
||||
"""
|
||||
|
||||
XCTAssertEqual(sqlScript?[0], oneStatement)
|
||||
XCTAssertEqual(sqlScript?[1], twoStatement)
|
||||
}
|
||||
}
|
||||
91
Tests/DataLiteCoreTests/Structures/SQLiteRowTests.swift
Normal file
91
Tests/DataLiteCoreTests/Structures/SQLiteRowTests.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
import XCTest
|
||||
import DataLiteCore
|
||||
|
||||
final class SQLiteRowTests: XCTestCase {
|
||||
func testInitEmptyRow() {
|
||||
let row = SQLiteRow()
|
||||
XCTAssertTrue(row.isEmpty)
|
||||
XCTAssertEqual(row.count, 0)
|
||||
}
|
||||
|
||||
func testUpdateColumnPosition() {
|
||||
var row = SQLiteRow()
|
||||
row["name"] = .text("Alice")
|
||||
row["age"] = .int(30)
|
||||
|
||||
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"] = SQLiteRawValue.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
|
||||
XCTAssertEqual(row.count, 1)
|
||||
}
|
||||
|
||||
func testIteration() {
|
||||
var row = SQLiteRow()
|
||||
row["name"] = .text("Alice")
|
||||
row["age"] = .int(30)
|
||||
row["city"] = .text("Wonderland")
|
||||
|
||||
var elements: [SQLiteRow.Element] = []
|
||||
for (column, value) in row {
|
||||
elements.append((column, value))
|
||||
}
|
||||
|
||||
XCTAssertEqual(elements.count, 3)
|
||||
XCTAssertEqual(elements[0].column, "name")
|
||||
XCTAssertEqual(elements[0].value, .text("Alice"))
|
||||
XCTAssertEqual(elements[1].column, "age")
|
||||
XCTAssertEqual(elements[1].value, .int(30))
|
||||
XCTAssertEqual(elements[2].column, "city")
|
||||
XCTAssertEqual(elements[2].value, .text("Wonderland"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user