Skip to content

Commit 3977813

Browse files
authored
Merge pull request #1167 from stephencelis/NSData-blobs
Reactivate "Performance improvements for Blob"
2 parents a4c1d1d + 06e55e7 commit 3977813

File tree

10 files changed

+97
-42
lines changed

10 files changed

+97
-42
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
* Fix `insertMany([Encodable])` ([#1130][], [#1138][])
1111
* Fix incorrect spelling of `remove_diacritics` ([#1128][])
1212
* Fix project build order ([#1131][])
13-
* Performance improvements ([#1109][], [#1115][], [#1132][])
13+
* Blob performance improvements ([#416][], [#1167][])
14+
* Various performance improvements ([#1109][], [#1115][], [#1132][])
1415
* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144][])
1516

1617
0.13.3 (27-03-2022), [diff][diff-0.13.3]
@@ -130,6 +131,7 @@
130131
[#30]: https://github.com/stephencelis/SQLite.swift/issues/30
131132
[#142]: https://github.com/stephencelis/SQLite.swift/issues/142
132133
[#315]: https://github.com/stephencelis/SQLite.swift/issues/315
134+
[#416]: https://github.com/stephencelis/SQLite.swift/pull/416
133135
[#426]: https://github.com/stephencelis/SQLite.swift/pull/426
134136
[#481]: https://github.com/stephencelis/SQLite.swift/pull/481
135137
[#532]: https://github.com/stephencelis/SQLite.swift/issues/532
@@ -189,3 +191,4 @@
189191
[#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144
190192
[#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146
191193
[#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148
194+
[#1167]: https://github.com/stephencelis/SQLite.swift/pull/1167

Sources/SQLite/Core/Blob.swift

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,50 @@
2121
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2222
// THE SOFTWARE.
2323
//
24+
import Foundation
2425

25-
public struct Blob {
26+
public final class Blob {
2627

27-
public let bytes: [UInt8]
28+
public let data: NSData
2829

29-
public init(bytes: [UInt8]) {
30-
self.bytes = bytes
30+
public var bytes: UnsafePointer<UInt8> {
31+
data.bytes.assumingMemoryBound(to: UInt8.self)
3132
}
3233

33-
public init(bytes: UnsafeRawPointer, length: Int) {
34-
let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length)
35-
self.init(bytes: [UInt8](i8bufptr))
34+
public var length: Int {
35+
data.count
3636
}
3737

38-
public func toHex() -> String {
39-
bytes.map {
40-
($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false)
41-
}.joined(separator: "")
38+
public convenience init(bytes: [UInt8]) {
39+
guard bytes.count > 0 else {
40+
self.init(data: NSData())
41+
return
42+
}
43+
self.init(data: NSData(bytes: bytes, length: bytes.count))
44+
}
45+
46+
public convenience init(bytes: UnsafeRawPointer, length: Int) {
47+
self.init(data: NSData(bytes: bytes, length: length))
48+
}
49+
50+
public init(data: NSData) {
51+
precondition(!(data is NSMutableData), "Blob cannot be initialized with mutable data")
52+
self.data = data
4253
}
4354

55+
public func toHex() -> String {
56+
guard length > 0 else { return "" }
57+
58+
var hex = ""
59+
for idx in 0..<length {
60+
let byte = bytes.advanced(by: idx).pointee
61+
if byte < 16 {
62+
hex += "0"
63+
}
64+
hex += String(byte, radix: 16, uppercase: false)
65+
}
66+
return hex
67+
}
4468
}
4569

4670
extension Blob: CustomStringConvertible {
@@ -56,5 +80,5 @@ extension Blob: Equatable {
5680
}
5781

5882
public func ==(lhs: Blob, rhs: Blob) -> Bool {
59-
lhs.bytes == rhs.bytes
83+
lhs.data == rhs.data
6084
}

Sources/SQLite/Core/Connection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ extension Context {
741741
func set(result: Binding?) {
742742
switch result {
743743
case let blob as Blob:
744-
sqlite3_result_blob(self, blob.bytes, Int32(blob.bytes.count), nil)
744+
sqlite3_result_blob(self, blob.bytes, Int32(blob.length), nil)
745745
case let double as Double:
746746
sqlite3_result_double(self, double)
747747
case let int as Int64:

Sources/SQLite/Core/Statement.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,24 @@ public final class Statement {
100100
}
101101

102102
fileprivate func bind(_ value: Binding?, atIndex idx: Int) {
103-
if value == nil {
103+
switch value {
104+
case .none:
104105
sqlite3_bind_null(handle, Int32(idx))
105-
} else if let value = value as? Blob {
106-
sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT)
107-
} else if let value = value as? Double {
106+
case let value as Blob where value.length == 0:
107+
sqlite3_bind_zeroblob(handle, Int32(idx), 0)
108+
case let value as Blob:
109+
sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)
110+
case let value as Double:
108111
sqlite3_bind_double(handle, Int32(idx), value)
109-
} else if let value = value as? Int64 {
112+
case let value as Int64:
110113
sqlite3_bind_int64(handle, Int32(idx), value)
111-
} else if let value = value as? String {
114+
case let value as String:
112115
sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)
113-
} else if let value = value as? Int {
116+
case let value as Int:
114117
self.bind(value.datatypeValue, atIndex: idx)
115-
} else if let value = value as? Bool {
118+
case let value as Bool:
116119
self.bind(value.datatypeValue, atIndex: idx)
117-
} else if let value = value {
120+
case .some(let value):
118121
fatalError("tried to bind unexpected value \(value)")
119122
}
120123
}

Sources/SQLite/Extensions/Cipher.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension Connection {
3232
}
3333

3434
public func key(_ key: Blob, db: String = "main") throws {
35-
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
35+
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length)
3636
}
3737

3838
/// Same as `key(_ key: String, db: String = "main")`, running "PRAGMA cipher_migrate;"
@@ -53,7 +53,7 @@ extension Connection {
5353

5454
/// Same as `[`keyAndMigrate(_ key: String, db: String = "main")` accepting byte array as key
5555
public func keyAndMigrate(_ key: Blob, db: String = "main") throws {
56-
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true)
56+
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length, migrate: true)
5757
}
5858

5959
/// Change the key on an open database. NB: only works if the database is already encrypted.
@@ -68,7 +68,7 @@ extension Connection {
6868
}
6969

7070
public func rekey(_ key: Blob, db: String = "main") throws {
71-
try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
71+
try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.length)
7272
}
7373

7474
/// Converts a non-encrypted database to an encrypted one.

Sources/SQLite/Foundation.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,11 @@ extension Data: Value {
3131
}
3232

3333
public static func fromDatatypeValue(_ dataValue: Blob) -> Data {
34-
Data(dataValue.bytes)
34+
dataValue.data as Data
3535
}
3636

3737
public var datatypeValue: Blob {
38-
withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in
39-
Blob(bytes: pointer.baseAddress!, length: count)
40-
}
38+
Blob(data: self as NSData)
4139
}
4240

4341
}

Tests/SQLiteTests/Core/BlobTests.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,47 @@ class BlobTests: XCTestCase {
55

66
func test_toHex() {
77
let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255])
8-
98
XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff")
109
}
1110

11+
func test_toHex_empty() {
12+
let blob = Blob(bytes: [])
13+
XCTAssertEqual(blob.toHex(), "")
14+
}
15+
16+
func test_description() {
17+
let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255])
18+
XCTAssertEqual(blob.description, "x'000a141e28323c46505a6496faff'")
19+
}
20+
21+
func test_description_empty() {
22+
let blob = Blob(bytes: [])
23+
XCTAssertEqual(blob.description, "x''")
24+
}
25+
1226
func test_init_array() {
13-
let blob = Blob(bytes: [42, 42, 42])
14-
XCTAssertEqual(blob.bytes, [42, 42, 42])
27+
let blob = Blob(bytes: [42, 43, 44])
28+
XCTAssertEqual([UInt8](blob.data), [42, 43, 44])
1529
}
1630

1731
func test_init_unsafeRawPointer() {
1832
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 3)
1933
pointer.initialize(repeating: 42, count: 3)
2034
let blob = Blob(bytes: pointer, length: 3)
21-
XCTAssertEqual(blob.bytes, [42, 42, 42])
35+
XCTAssertEqual([UInt8](blob.data), [42, 42, 42])
36+
}
37+
38+
func test_equality() {
39+
let blob1 = Blob(bytes: [42, 42, 42])
40+
let blob2 = Blob(bytes: [42, 42, 42])
41+
let blob3 = Blob(bytes: [42, 42, 43])
42+
43+
XCTAssertEqual(Blob(bytes: []), Blob(bytes: []))
44+
XCTAssertEqual(blob1, blob2)
45+
XCTAssertNotEqual(blob1, blob3)
46+
}
47+
48+
func XXX_test_init_with_mutable_data_fails() {
49+
_ = Blob(data: NSMutableData())
2250
}
2351
}

Tests/SQLiteTests/Core/StatementTests.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class StatementTests: SQLiteTestCase {
2222
let statement = try db.prepare("SELECT email FROM users")
2323
XCTAssert(try statement.step())
2424
let blob = statement.row[0] as Blob
25-
XCTAssertEqual("[email protected]", String(bytes: blob.bytes, encoding: .utf8)!)
25+
XCTAssertEqual("[email protected]", String(data: blob.data as Data, encoding: .utf8)!)
2626
}
2727

2828
func test_zero_sized_blob_returns_null() throws {
@@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase {
3131
try db.run(blobs.create { $0.column(blobColumn) })
3232
try db.run(blobs.insert(blobColumn <- Blob(bytes: [])))
3333
let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0))
34-
XCTAssertEqual([], blobValue.bytes)
34+
XCTAssertEqual([], [UInt8](blobValue.data))
3535
}
3636

3737
func test_prepareRowIterator() throws {
@@ -71,5 +71,4 @@ class StatementTests: SQLiteTestCase {
7171
// truncate succeeds
7272
try db.run("DROP TABLE users")
7373
}
74-
7574
}

Tests/SQLiteTests/Extensions/CipherTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class CipherTests: XCTestCase {
1919

2020
// db2
2121
let key2 = keyData()
22-
try db2.key(Blob(bytes: key2.bytes, length: key2.length))
22+
try db2.key(Blob(data: key2))
2323

2424
try db2.run("CREATE TABLE foo (bar TEXT)")
2525
try db2.run("INSERT INTO foo (bar) VALUES ('world')")
@@ -47,7 +47,7 @@ class CipherTests: XCTestCase {
4747

4848
func test_data_rekey() throws {
4949
let newKey = keyData()
50-
try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length))
50+
try db2.rekey(Blob(data: newKey))
5151
XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64)
5252
}
5353

@@ -108,12 +108,12 @@ class CipherTests: XCTestCase {
108108
XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64)
109109
}
110110

111-
private func keyData(length: Int = 64) -> NSMutableData {
111+
private func keyData(length: Int = 64) -> NSData {
112112
let keyData = NSMutableData(length: length)!
113113
let result = SecRandomCopyBytes(kSecRandomDefault, length,
114114
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
115115
XCTAssertEqual(0, result)
116-
return keyData
116+
return NSData(data: keyData)
117117
}
118118
}
119119
#endif

Tests/SQLiteTests/FoundationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class FoundationTests: XCTestCase {
55
func testDataFromBlob() {
66
let data = Data([1, 2, 3])
77
let blob = data.datatypeValue
8-
XCTAssertEqual([1, 2, 3], blob.bytes)
8+
XCTAssertEqual([1, 2, 3], [UInt8](blob.data))
99
}
1010

1111
func testBlobToData() {

0 commit comments

Comments
 (0)