Skip to content

Commit cc3b0b1

Browse files
planetMDXRoland Ambs
and
Roland Ambs
authored
Add support for new types added in ClickHouseNIO 1.4 (#4)
* added support for new types, updated tests * added tests for arrays, updated array cardinality * fixed bool cardinality (Bool and Dates have to be enabled on the server) * updated dependency, removed fixed todo Co-authored-by: Roland Ambs <[email protected]>
1 parent 5e5b14f commit cc3b0b1

6 files changed

+194
-12
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let package = Package(
1212
],
1313
dependencies: [
1414
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
15-
.package(url: "https://github.com/patrick-zippenfenig/ClickHouseNIO.git", from: "1.3.0")
15+
.package(url: "https://github.com/patrick-zippenfenig/ClickHouseNIO.git", from: "1.4.1")
1616
],
1717
targets: [
1818
.target(

Sources/ClickHouseVapor/Application+ClickHouseNIO.swift

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import ClickHouseNIO
1010

1111
@_exported import struct NIO.TimeAmount
1212

13+
@_exported import struct ClickHouseNIO.ClickHouseDate
14+
@_exported import struct ClickHouseNIO.ClickHouseDate32
15+
@_exported import struct ClickHouseNIO.ClickHouseDateTime
16+
@_exported import struct ClickHouseNIO.ClickHouseDateTime64
17+
@_exported import struct ClickHouseNIO.ClickHouseEnum8
18+
@_exported import struct ClickHouseNIO.ClickHouseEnum16
19+
1320
/// Vapor `Application.ClickHouse` and `Request.ClickHouse` implement this procotol to be used later for queries
1421
public protocol ClickHouseConnectionProtocol {
1522
var eventLoop: EventLoop { get }

Sources/ClickHouseVapor/ClickHouseColumnConvertible.swift

+105-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import ClickHouseNIO
99

10+
11+
1012
/// Define how a colun can be converted into a clickhose datatype
1113
public protocol ClickHouseColumnConvertible: AnyObject {
1214
var key: String { get }
@@ -30,7 +32,7 @@ public protocol ClickHouseColumnConvertible: AnyObject {
3032
public protocol ClickHouseColumnConvertibleTyped: ClickHouseColumnConvertible {
3133
associatedtype Value: ClickHouseDataType
3234
var wrappedValue: [Value] { get set }
33-
var fixedStringLen: Int? { get }
35+
var columnMetadata: ClickHouseColumnMetadata? { get }
3436

3537
}
3638

@@ -70,7 +72,7 @@ extension ClickHouseColumnConvertibleTyped {
7072
}
7173

7274
public func clickHouseTypeName() -> ClickHouseTypeName {
73-
return Value.getClickHouseTypeName(fixedLength: fixedStringLen)
75+
return Value.getClickHouseTypeName(columnMetadata: columnMetadata)
7476
}
7577
}
7678

@@ -81,26 +83,123 @@ public final class Field<Value: ClickHouseDataType>: ClickHouseColumnConvertible
8183
public let isPrimary: Bool
8284
public let isOrderBy: Bool
8385
public let isLowCardinality: Bool
84-
public let fixedStringLen: Int?
86+
public let columnMetadata: ClickHouseColumnMetadata?
8587

8688
public var projectedValue: Field<Value> {
8789
self
8890
}
8991

90-
public init(
92+
93+
fileprivate init(
9194
key: String,
9295
isPrimary: Bool = false,
9396
isOrderBy: Bool = false,
9497
isLowCardinality: Bool = false,
95-
fixedStringLen: Int? = nil
98+
columnMetadata: ClickHouseColumnMetadata
9699
) {
97100
self.key = key
98101
self.isPrimary = isPrimary
99102
self.isOrderBy = isOrderBy
100103
self.isLowCardinality = isLowCardinality
101-
self.fixedStringLen = fixedStringLen
104+
self.columnMetadata = columnMetadata
102105
self.wrappedValue = []
103106
}
107+
public init(
108+
key: String,
109+
isPrimary: Bool = false,
110+
isOrderBy: Bool = false,
111+
isLowCardinality: Bool = false
112+
) {
113+
self.key = key
114+
self.isPrimary = isPrimary
115+
self.isOrderBy = isOrderBy
116+
self.isLowCardinality = isLowCardinality
117+
self.columnMetadata = nil
118+
self.wrappedValue = []
119+
}
120+
}
121+
122+
extension Field where Value == String {
123+
convenience init(
124+
key: String,
125+
isPrimary: Bool = false,
126+
isOrderBy: Bool = false,
127+
isLowCardinality: Bool = false,
128+
fixedStringLen: Int
129+
) {
130+
self.init(key: key, isPrimary: isPrimary, isOrderBy: isOrderBy, isLowCardinality: isLowCardinality, columnMetadata: .fixedStringLength(fixedStringLen))
131+
}
132+
}
133+
extension Field where Value == ClickHouseDateTime {
134+
convenience init(
135+
key: String,
136+
isPrimary: Bool = false,
137+
isOrderBy: Bool = false,
138+
isLowCardinality: Bool = false,
139+
timeZone: String? = nil
140+
) {
141+
self.init(key: key, isPrimary: isPrimary, isOrderBy: isOrderBy, isLowCardinality: isLowCardinality, columnMetadata: .dateTimeTimeZone(timeZone))
142+
}
143+
144+
}
145+
extension Field where Value == ClickHouseDateTime64 {
146+
convenience init(
147+
key: String,
148+
isPrimary: Bool = false,
149+
isOrderBy: Bool = false,
150+
isLowCardinality: Bool = false,
151+
precision: Int,
152+
timeZone: String? = nil
153+
) {
154+
self.init(key: key, isPrimary: isPrimary, isOrderBy: isOrderBy, isLowCardinality: isLowCardinality, columnMetadata: .dateTime64Precision(precision, timeZone))
155+
}
156+
convenience init(
157+
key: String,
158+
isPrimary: Bool = false,
159+
isOrderBy: Bool = false,
160+
isLowCardinality: Bool = false
161+
) {
162+
fatalError("missing precision for DateTime64")
163+
}
164+
}
165+
extension Field where Value == ClickHouseEnum8 {
166+
convenience init(
167+
key: String,
168+
isPrimary: Bool = false,
169+
isOrderBy: Bool = false,
170+
isLowCardinality: Bool = false,
171+
mapping: [String: Int8]
172+
) {
173+
self.init(key: key, isPrimary: isPrimary, isOrderBy: isOrderBy, isLowCardinality: isLowCardinality, columnMetadata: .enum8Map(mapping))
174+
}
175+
176+
convenience init(
177+
key: String,
178+
isPrimary: Bool = false,
179+
isOrderBy: Bool = false,
180+
isLowCardinality: Bool = false
181+
) {
182+
fatalError("missing enum-mapping for enum8")
183+
}
184+
}
185+
extension Field where Value == ClickHouseEnum16 {
186+
convenience init(
187+
key: String,
188+
isPrimary: Bool = false,
189+
isOrderBy: Bool = false,
190+
isLowCardinality: Bool = false,
191+
mapping: [String: Int16]
192+
) {
193+
self.init(key: key, isPrimary: isPrimary, isOrderBy: isOrderBy, isLowCardinality: isLowCardinality, columnMetadata: .enum16Map(mapping))
194+
}
195+
convenience init(
196+
key: String,
197+
isPrimary: Bool = false,
198+
isOrderBy: Bool = false,
199+
isLowCardinality: Bool = false
200+
) {
201+
fatalError("missing enum-mapping for enum16")
202+
}
104203
}
105204

106205
extension Array {

Sources/ClickHouseVapor/ClickHouseEngine.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ extension ClickHouseTypeName {
117117
return true
118118
case .nullable(let type):
119119
return type.supportsLowCardinality
120-
}
120+
case .array(let type):
121+
return false
122+
case .boolean:
123+
return false
124+
case .date:
125+
return false
126+
case .date32:
127+
return false
128+
case .dateTime(_):
129+
return false
130+
case .dateTime64(_):
131+
return false
132+
case .enum16(_):
133+
return false
134+
case .enum8(_):
135+
return false
136+
}
121137
}
122138
}

Sources/ClickHouseVapor/ClickHouseModel.swift

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Foundation
99
import Vapor
1010
import ClickHouseNIO
1111

12+
1213
public protocol ClickHouseModel: AnyObject {
1314
static var engine: ClickHouseEngine { get }
1415
init()

Tests/ClickHouseVaporTests/ClickHouseVaporTests.swift

+63-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import XCTest
22
@testable import ClickHouseVapor
3+
import Foundation
34
import Vapor
45

56
extension Application {
@@ -29,6 +30,39 @@ public class TestModel: ClickHouseModel {
2930
@Field(key: "fixed", isLowCardinality: true, fixedStringLen: 10)
3031
var fixed: [ String ]
3132

33+
@Field(key: "arr")
34+
var arr: [ [Int64] ]
35+
36+
/// Not implemented on test-server
37+
// @Field(key: "bol")
38+
// var bol: [ Bool ]
39+
40+
@Field(key: "dat")
41+
var dat: [ClickHouseDate]
42+
43+
44+
/// Not implemented on test-server
45+
// @Field(key: "dat32")
46+
// var dat32: [ClickHouseDate32]
47+
48+
@Field(key: "datt")
49+
var datt: [ ClickHouseDateTime ]
50+
51+
@Field(key: "dattz", timeZone: "'GMT'")
52+
var dattz: [ ClickHouseDateTime ]
53+
54+
@Field(key: "datt64", precision: 3)
55+
var datt64: [ ClickHouseDateTime64 ]
56+
57+
@Field(key: "datt64z", precision: 3,timeZone: "'GMT'")
58+
var datt64z: [ ClickHouseDateTime64 ]
59+
60+
@Field(key: "en8", mapping: ["a": 0, "b": 1])
61+
var en8: [ ClickHouseEnum8 ]
62+
63+
@Field(key: "en16", mapping: ["a": 12, "b": 1, "c": 600])
64+
var en16: [ ClickHouseEnum16 ]
65+
3266
@Field(key: "temperature")
3367
var temperature: [Float]
3468

@@ -76,25 +110,50 @@ final class ClickHouseVaporTests: XCTestCase {
76110

77111
model.id = [ "x010", "ax51", "cd22" ]
78112
model.fixed = [ "", "123456", "12345678901234" ]
113+
model.arr = [[1], [], [76, 56, 2]]
114+
model.dat = [.clickhouseDefault, .clickhouseDefault, .clickhouseDefault]
115+
model.datt = [.clickhouseDefault, .clickhouseDefault, .clickhouseDefault]
116+
model.datt64 = [.clickhouseDefault, .clickhouseDefault, .clickhouseDefault]
117+
model.datt64z = [.clickhouseDefault, .clickhouseDefault, .clickhouseDefault]
118+
model.dattz = [.clickhouseDefault, .clickhouseDefault, .clickhouseDefault]
119+
model.en8 = [.init(word: "a"), .init(word: "b"), .init(word: "a")]
120+
model.en16 = [.init(word: "a"), .init(word: "b"), .init(word: "c")]
79121
model.timestamp = [ 100, 200, 300 ]
80122
model.temperature = [ 11.1, 10.4, 8.9 ]
81123

82124
let createQuery = TestModel.engine.createTableQuery(columns: model.properties)
83-
XCTAssertEqual(createQuery, """
84-
CREATE TABLE IF NOT EXISTS `test` (timestamp Int64,stationID LowCardinality(String),fixed LowCardinality(FixedString(10)),temperature Float32)
125+
XCTAssertEqual(createQuery
126+
.replacingOccurrences(of: "Enum8('b'=1,'a'=0)", with: "Enum8('a'=0,'b'=1)")
127+
.replacingOccurrences(of: "Enum16('b'=1,'a'=12,'c'=600)", with: "Enum16('a'=12,'b'=1,'c'=600)")
128+
.replacingOccurrences(of: "Enum16('b'=1,'c'=600,'a'=12)", with: "Enum16('a'=12,'b'=1,'c'=600)")
129+
.replacingOccurrences(of: "Enum16('a'=12,'c'=600,'b'=1)", with: "Enum16('a'=12,'b'=1,'c'=600)")
130+
.replacingOccurrences(of: "Enum16('c'=600,'b'=1,'a'=12)", with: "Enum16('a'=12,'b'=1,'c'=600)")
131+
.replacingOccurrences(of: "Enum16('c'=600,'a'=12,'b'=1)", with: "Enum16('a'=12,'b'=1,'c'=600)"),
132+
"""
133+
CREATE TABLE IF NOT EXISTS `test` (timestamp Int64,stationID LowCardinality(String),fixed LowCardinality(FixedString(10)),arr Array(Int64),dat Date,datt DateTime,dattz DateTime('GMT'),datt64 DateTime64(3),datt64z DateTime64(3, 'GMT'),en8 Enum8('a'=0,'b'=1),en16 Enum16('a'=12,'b'=1,'c'=600),temperature Float32)
85134
ENGINE = ReplacingMergeTree()
86135
PRIMARY KEY (timestamp,stationID) PARTITION BY (toYYYYMM(toDateTime(timestamp))) ORDER BY (timestamp,stationID)
87136
""")
88-
89137
try! TestModel.createTable(on: app.clickHouse).wait()
90138
try! model.insert(on: app.clickHouse).wait()
91-
92139
let model2 = try! TestModel.select(on: app.clickHouse).wait()
93140

94141
XCTAssertEqual(model.temperature, model2.temperature)
95142
XCTAssertEqual(model.id, model2.id)
96143
XCTAssertEqual(["", "123456", "1234567890"], model2.fixed)
97144
XCTAssertEqual(model.timestamp, model2.timestamp)
145+
XCTAssertEqual(model.dat.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
146+
XCTAssertEqual(model2.dat.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
147+
XCTAssertEqual(model.datt.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
148+
XCTAssertEqual(model2.datt.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
149+
XCTAssertEqual(model.dattz.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
150+
XCTAssertEqual(model2.dattz.map { $0.date}, [Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0), Date(timeIntervalSince1970: 0.0)])
151+
XCTAssertEqual(model.en8.map { $0.word}, ["a", "b", "a"])
152+
XCTAssertEqual(model2.en8.map { $0.word}, ["a", "b", "a"])
153+
XCTAssertEqual(model.en16.map { $0.word}, ["a", "b", "c"])
154+
XCTAssertEqual(model2.en16.map { $0.word}, ["a", "b", "c"])
155+
XCTAssertEqual(model.arr, [[1], [], [76, 56, 2]])
156+
XCTAssertEqual(model2.arr, [[1], [], [76, 56, 2]])
98157

99158
let filtered = try! TestModel.select(
100159
on: app.clickHouse,

0 commit comments

Comments
 (0)