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)
}
}

View 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")
}
}
}

View 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)
}
}

View 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")
}
}

View File

@@ -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)
}
}

View 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)
}
}

View 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)
}
}

View 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)
}
}

View 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)
}
}

View File

@@ -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
}
}

View 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)
}
}

View 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)
}
}

View 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)
}
}

View File

@@ -0,0 +1 @@

Binary file not shown.

View 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

View 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)
}
}

View 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"))
}
}