diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index cd31483b..a7fffdc0 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -58,3 +58,23 @@ extension Blob: Equatable { public func ==(lhs: Blob, rhs: Blob) -> Bool { lhs.bytes == rhs.bytes } + +extension Blob: Comparable { + public static func < (lhs: Blob, rhs: Blob) -> Bool { + // put most sig digit at the end + let lBytes: [UInt8] = lhs.bytes.reversed() + let rBytes: [UInt8] = rhs.bytes.reversed() + + for byteIndex in stride(from: max(lhs.bytes.count, rhs.bytes.count) - 1, to: 0, by: -1) { + let lVal = byteIndex < lBytes.count ? lBytes[byteIndex] : 0 + let rVal = byteIndex < rBytes.count ? rBytes[byteIndex] : 0 + if lVal < rVal { + return true + } else if lVal > rVal { + return false + } + } + return true // lhs.bytes == rhs.bytes + } + +} diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1e2489b5..c9d9f944 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -108,6 +108,10 @@ public final class Statement { sqlite3_bind_double(handle, Int32(idx), value) } else if let value = value as? Int64 { sqlite3_bind_int64(handle, Int32(idx), value) + } else if let value = value as? UInt64 { + sqlite3_bind_blob(handle, Int32(idx), value.datatypeValue.bytes, Int32(value.datatypeValue.bytes.count), SQLITE_TRANSIENT) + } else if let value = value as? UInt32 { + sqlite3_bind_int64(handle, Int32(idx), value.datatypeValue) } else if let value = value as? String { sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) } else if let value = value as? Int { diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index 9c463f0c..64541d57 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -73,6 +73,27 @@ extension Int64: Number, Value { } +extension UInt64: Number, Value { + + public static let declaredDatatype = Blob.declaredDatatype + + public static func fromDatatypeValue(_ datatypeValue: Blob) -> UInt64 { + guard datatypeValue.bytes.count >= MemoryLayout.size else { return 0 } + let bigEndianUInt64 = datatypeValue.bytes.withUnsafeBytes({ $0.load(as: UInt64.self )}) + return UInt64(bigEndian: bigEndianUInt64) + } + + public var datatypeValue: Blob { + var bytes: [UInt8] = [] + withUnsafeBytes(of: self) { pointer in + // little endian by default on iOS/macOS, so reverse to get bigEndian + bytes.append(contentsOf: pointer.reversed()) + } + return Blob(bytes: bytes) + } + +} + extension String: Binding, Value { public static let declaredDatatype = "TEXT" @@ -130,3 +151,17 @@ extension Int: Number, Value { } } + +extension UInt32: Number, Value { + + public static var declaredDatatype = Int64.declaredDatatype + + public static func fromDatatypeValue(_ datatypeValue: Int64) -> UInt32 { + UInt32(datatypeValue) + } + + public var datatypeValue: Int64 { + Int64(self) + } + +} diff --git a/Tests/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/AggregateFunctionsTests.swift index 71ac79fb..952d5e74 100644 --- a/Tests/SQLiteTests/AggregateFunctionsTests.swift +++ b/Tests/SQLiteTests/AggregateFunctionsTests.swift @@ -21,6 +21,8 @@ class AggregateFunctionsTests: XCTestCase { func test_max_wrapsComparableExpressionsWithMaxFunction() { assertSQL("max(\"int\")", int.max) assertSQL("max(\"intOptional\")", intOptional.max) + assertSQL("max(\"uint64\")", uint64.max) + assertSQL("max(\"uint64Optional\")", uint64Optional.max) assertSQL("max(\"double\")", double.max) assertSQL("max(\"doubleOptional\")", doubleOptional.max) assertSQL("max(\"string\")", string.max) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index cef485fc..0c1d0f89 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -25,4 +25,17 @@ class FoundationTests: XCTestCase { let uuid = UUID.fromDatatypeValue(string) XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } + + func testCompareBlob() { + let data1 = Data([1, 2, 3]) + let data2 = Data([1, 3, 3]) + let data3 = Data([4, 3]) + let blob1 = data1.datatypeValue + let blob2 = data2.datatypeValue + let blob3 = data3.datatypeValue + XCTAssert(blob1 < blob2) + XCTAssert(blob2 > blob1) + XCTAssert(blob1 > blob3) + XCTAssert(blob2 > blob3) + } } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 495a5e51..49390421 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -27,6 +27,10 @@ class SchemaTests: XCTestCase { "\"doubleOptional\" REAL, " + "\"int64\" INTEGER NOT NULL, " + "\"int64Optional\" INTEGER, " + + "\"uint32\" INTEGER NOT NULL, " + + "\"uint32Optional\" INTEGER, " + + "\"uint64\" BLOB NOT NULL, " + + "\"uint64Optional\" BLOB, " + "\"string\" TEXT NOT NULL, " + "\"stringOptional\" TEXT" + ")", @@ -37,6 +41,10 @@ class SchemaTests: XCTestCase { t.column(doubleOptional) t.column(int64) t.column(int64Optional) + t.column(uint32) + t.column(uint32Optional) + t.column(uint64) + t.column(uint64Optional) t.column(string) t.column(stringOptional) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index d1126b66..1e8038b2 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -14,31 +14,46 @@ class SelectTests: SQLiteTestCase { CREATE TABLE users_name ( id INTEGER, user_id INTEGER REFERENCES users(id), - name TEXT + name TEXT, + step_count BLOB, + stair_count INTEGER ) """ ) } - func test_select_columns_from_multiple_tables() { + func test_select_columns_from_multiple_tables() throws { let usersData = Table("users_name") let users = Table("users") let name = Expression("name") let id = Expression("id") let userID = Expression("user_id") + let stepCount = Expression("step_count") + let stairCount = Expression("stair_count") let email = Expression("email") + // use UInt64.max - 1 to test Endianness - it should store/load as big endian + let reallyBigNumber = UInt64.max - 1 + let prettyBigNumber = UInt32.max - 1 try! insertUser("Joey") try! db.run(usersData.insert( id <- 1, userID <- 1, - name <- "Joey" + name <- "Joey", + stepCount <- reallyBigNumber, + stairCount <- prettyBigNumber )) - try! db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { + try! db.prepare(users.select(name, email, stepCount, stairCount).join(usersData, on: userID == users[id])).forEach { XCTAssertEqual($0[name], "Joey") XCTAssertEqual($0[email], "Joey@example.com") + XCTAssertEqual($0[stepCount], reallyBigNumber) + XCTAssertEqual($0[stairCount], prettyBigNumber) } + + // ensure we can bind UInt64 and UInt32 + _ = try db.run("SELECT * FROM \"users_name\" WHERE step_count = ? AND stair_count = ?", + reallyBigNumber, prettyBigNumber) } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 07cc6f06..b1e5ab8b 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -92,6 +92,12 @@ let intOptional = Expression("intOptional") let int64 = Expression("int64") let int64Optional = Expression("int64Optional") +let uint32 = Expression("uint32") +let uint32Optional = Expression("uint32Optional") + +let uint64 = Expression("uint64") +let uint64Optional = Expression("uint64Optional") + let string = Expression("string") let stringOptional = Expression("stringOptional")