Skip to content

Commit

Permalink
Merge pull request #4 from patrick-zippenfenig/bug/uuid-encoding
Browse files Browse the repository at this point in the history
Bug/UUID encoding
  • Loading branch information
patrick-zippenfenig authored Dec 8, 2020
2 parents cfe3f2d + 772371e commit d68f0d4
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
46 changes: 46 additions & 0 deletions Sources/ClickHouseNIO/ByteBufferExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ extension ByteBuffer {
return array
}

mutating func readUuidArray(numRows: Int, endianness: Endianness = .big) -> [UUID]? {
guard readableBytes >= MemoryLayout<UUID>.size * numRows else {
return nil
}
return [UUID](unsafeUninitializedCapacity: numRows) { (buffer, initializedCount) in
let numBytes = readableBytesView.withUnsafeBytes({ $0.copyBytes(to: buffer)})
assert(numBytes / MemoryLayout<UUID>.size == numRows)
moveReaderIndex(forwardBy: numBytes)
if endianness == .little {
for (i,e) in buffer.enumerated() {
buffer[i] = e.swapBytes()
}
}
initializedCount = numRows
}
}

/**
Read bytes as a specific array type. The data type should be continuously stored in memory. E.g. Does not work with strings
TODO: Ensure that this works for all types... endians might also be an issue
Expand All @@ -167,4 +184,33 @@ extension ByteBuffer {
writeInteger(element, endianness: .little)
}
}

/// Write UUID array for clickhouse
mutating func writeUuidArray(_ array: [UUID], endianness: Endianness = .big) {
reserveCapacity(array.count * MemoryLayout<UUID>.size + writableBytes)
for element in array {
switch endianness {
case .big:
let _ = withUnsafeBytes(of: element) {
writeBytes($0)
}
case .little:
let _ = withUnsafeBytes(of: element.swapBytes()) {
writeBytes($0)
}
}


}
}
}

extension UUID {
/// Swap bytes before sending to clickhouse and after retrieval
fileprivate func swapBytes() -> UUID {
let bytes = self.uuid
let b = (bytes.7, bytes.6, bytes.5, bytes.4, bytes.3, bytes.2, bytes.1, bytes.0,
bytes.15, bytes.14, bytes.13, bytes.12, bytes.11, bytes.10, bytes.9, bytes.8)
return UUID(uuid: b)
}
}
6 changes: 2 additions & 4 deletions Sources/ClickHouseNIO/ClickHouseArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,7 @@ extension ByteBuffer {
writeBytes($0)
}
} else if let array = array as? [UUID] {
let _ = array.withUnsafeBytes {
writeBytes($0)
}
writeUuidArray(array, endianness: .little)
} else if let length = fixedLength, let array = array as? [String] {
writeClickHouseFixedStrings(array, length: length)
} else if let array = array as? [String] {
Expand Down Expand Up @@ -253,7 +251,7 @@ extension ByteBuffer {
}
return array
case .uuid:
guard let array: [UUID] = readUnsafeGenericArray(numRows: numRows) else {
guard let array = readUuidArray(numRows: numRows, endianness: .little) else {
return nil
}
return array
Expand Down
36 changes: 36 additions & 0 deletions Tests/ClickHouseNIOTests/ClickhouseNIOTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,40 @@ ENGINE = MergeTree() PRIMARY KEY stationid ORDER BY stationid
XCTAssertEqual((0..<count).map{String("\($0)".prefix(2))}, str)
}.wait()
}

func testUUID() {
try! conn.connection.command(sql: "DROP TABLE IF EXISTS test").wait()
let sql = """
CREATE TABLE test
(
id Int32,
uuid UUID
)
ENGINE = MergeTree() PRIMARY KEY id ORDER BY id
"""
try! conn.connection.command(sql: sql).wait()

let uuidStrings : [String] = ["ba4a9cd7-c69c-9fe8-5335-7631f448b597", "ad4f8401-88ff-ca3d-0443-e0163288f691", "5544beae-2370-c5e8-b8b6-c6c46156d28d"]
let uuids = uuidStrings.map { UUID(uuidString: $0)!}
let ids : [Int32] = [1, 2, 3]
print(uuids)
let data = [
ClickHouseColumn("id", ids),
ClickHouseColumn("uuid", uuids)
]

try! conn.connection.insert(into: "test", data: data).wait()

try! conn.connection.query(sql: "SELECT id, uuid, toString(uuid) as uuidString FROM test").map { res in
print(res)
guard let datatype = res.columns.first(where: {$0.name == "uuidString"})!.values as? [String] else {
fatalError("Column `uuidString`, was not a String array")
}
XCTAssertEqual(datatype, uuidStrings )
guard let id = res.columns.first(where: {$0.name == "id"})!.values as? [Int32] else {
fatalError("Column `id`, was not an Int32 array")
}
XCTAssertEqual(id, [1, 2, 3])
}.wait()
}
}

0 comments on commit d68f0d4

Please sign in to comment.