Skip to content

Commit bf912c3

Browse files
committed
feat: updated Point to be a sendable struct
Deprecated addTag(key:value:), addField(key:value:), and time(time:) in favor of using the mutable properties. BREAKING CHANGE: Point is now a value type which can cause breaking behaviour in code expecting the behaviour of a reference type. BREAKING CHANGE: addTag(key:value:), addField(key:value:), and time(time:) have mutating versions when called and the result is discarded. BREAKING CHANGE: Minimum swift version is now 5.7
1 parent 7876126 commit bf912c3

10 files changed

+466
-309
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Package.resolved
4444
#
4545
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
4646
# hence it is not needed unless you have added a package configuration file to your project
47-
# .swiftpm
47+
.swiftpm
4848

4949
.build/
5050

CHANGELOG.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
## 1.8.0 [unreleased]
1+
## 2.0.0 [unreleased]
2+
3+
`Point` has been updated from a class to a `Sendable` struct. This allows passing instances of `Point` across arbitrary concurrent contexts without introducing a risk of data races. Anywhere using `Point` will need to be updated to account for the change from a class to a struct.
4+
5+
The minimum swift version has been raised to 5.7 to support the `Sendable` protocol.
6+
7+
`addTag(key:value:)`, `addField(key:value:)`, and `time(time:)` have been deprecated in favor of passing values to `Point.init` or mutating the properties directly. Since the methods mutated the instance and returned the instance, they aren't compatible with `Point` becoming a struct. To simplify migration, each method now has two versions: one that mutates the instance and one that returns a new modified instance. The methods returning a new instance can still be chained together but the result must be assigned to a variable. The methods that mutate the instance can no longer be chained together, but don't require assigning the result to a variable.
8+
9+
### Features
10+
1. [#69](https://github.com/influxdata/influxdb-client-swift/pull/69) Updated `Point` to be a sendable struct
11+
1. [#70](https://github.com/influxdata/influxdb-client-swift/pull/70): Improved usability of `Point` by adding tags, fields, and time to the initializer and making the properties public and mutable
212

313
## 1.7.0 [2024-05-17]
414

Package.swift

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.3
1+
// swift-tools-version:5.7
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
@@ -11,21 +11,27 @@ let package = Package(
1111
],
1212
dependencies: [
1313
// Dependencies declare other packages that this package depends on.
14-
.package(name: "Gzip", url: "https://github.com/1024jp/GzipSwift", from: "5.1.1"),
15-
.package(name: "CSV.swift", url: "https://github.com/yaslab/CSV.swift", from: "2.4.2"),
16-
.package(name: "SwiftTestReporter", url: "https://github.com/allegro/swift-junit.git", from: "2.0.0"),
17-
.package(name: "swift-log", url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
14+
.package(url: "https://github.com/1024jp/GzipSwift", from: "5.1.1"),
15+
.package(url: "https://github.com/yaslab/CSV.swift", from: "2.4.2"),
16+
.package(url: "https://github.com/allegro/swift-junit.git", from: "2.0.0"),
17+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
1818
],
1919
targets: [
2020
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2121
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2222
.target(name: "InfluxDBSwift", dependencies: [
23-
"Gzip",
23+
.product(name: "Gzip", package: "GzipSwift"),
2424
.product(name: "CSV", package: "CSV.swift"),
2525
.product(name: "Logging", package: "swift-log")
2626
]),
2727
.target(name: "InfluxDBSwiftApis", dependencies: ["InfluxDBSwift"]),
28-
.testTarget(name: "InfluxDBSwiftTests", dependencies: ["InfluxDBSwift", "SwiftTestReporter"]),
29-
.testTarget(name: "InfluxDBSwiftApisTests", dependencies: ["InfluxDBSwiftApis", "SwiftTestReporter"]),
28+
.testTarget(name: "InfluxDBSwiftTests", dependencies: [
29+
"InfluxDBSwift",
30+
.product(name: "SwiftTestReporter", package: "swift-junit")
31+
]),
32+
.testTarget(name: "InfluxDBSwiftApisTests", dependencies: [
33+
"InfluxDBSwiftApis",
34+
.product(name: "SwiftTestReporter", package: "swift-junit")
35+
]),
3036
]
3137
)

Sources/InfluxDBSwift/InfluxDBClient.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import FoundationNetworking
2424
/// ````
2525
public struct InfluxDBClient {
2626
/// Version of client.
27-
public static var version: String = "1.8.0dev"
27+
public static var version: String = "2.0.0dev"
2828
/// InfluxDB host and port.
2929
public let url: String
3030
/// Authentication token.
@@ -247,7 +247,7 @@ extension InfluxDBClient {
247247

248248
/// An enum represents the precision for the unix timestamps within the body line-protocol.
249249
/// - SeeAlso: https://docs.influxdata.com/influxdb/latest/write-data/#timestamp-precision
250-
public enum TimestampPrecision: String, Codable, CaseIterable {
250+
public enum TimestampPrecision: String, Codable, CaseIterable, Sendable {
251251
/// Milliseconds
252252
case ms
253253
/// Seconds

Sources/InfluxDBSwift/Point.swift

+114-72
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,31 @@ extension InfluxDBClient {
88
/// Point defines the values that will be written to the database.
99
///
1010
/// - SeeAlso: http://bit.ly/influxdata-point
11-
public class Point {
11+
public struct Point: Sendable {
1212
/// The measurement name.
13-
private let measurement: String
13+
public let measurement: String
1414
// The measurement tags.
15-
private var tags: [String: String?] = [:]
15+
public var tags: [String: String]
1616
// The measurement fields.
17-
private var fields: [String: FieldValue?] = [:]
17+
public var fields: [String: FieldValue]
1818
/// The data point time.
19-
var time: TimestampValue?
19+
public var time: TimestampValue?
2020

2121
/// Create a new Point with specified a measurement name and precision.
2222
///
2323
/// - Parameters:
2424
/// - measurement: the measurement name
2525
/// - precision: the data point precision
26-
public init(_ measurement: String) {
26+
public init(
27+
_ measurement: String,
28+
tags: [String: String] = [:],
29+
fields: [String: FieldValue] = [:],
30+
time: TimestampValue? = nil
31+
) {
2732
self.measurement = measurement
28-
}
29-
30-
/// Adds or replaces a tag value for this point.
31-
///
32-
/// - Parameters:
33-
/// - key: the tag name
34-
/// - value: the tag value
35-
/// - Returns: self
36-
@discardableResult
37-
public func addTag(key: String?, value: String?) -> Point {
38-
if let key = key {
39-
tags[key] = value
40-
}
41-
return self
42-
}
43-
44-
/// Adds or replaces a field value for this point.
45-
///
46-
/// - Parameters:
47-
/// - key: the field name
48-
/// - value: the field value
49-
/// - Returns: self
50-
@discardableResult
51-
public func addField(key: String?, value: FieldValue?) -> Point {
52-
if let key = key {
53-
fields[key] = value
54-
}
55-
return self
56-
}
57-
58-
/// Updates the timestamp for the point.
59-
///
60-
/// - Parameters:
61-
/// - time: the timestamp. It can be `Int` or `Date`.
62-
/// - Returns: self
63-
@discardableResult
64-
public func time(time: TimestampValue) -> Point {
33+
self.tags = tags
34+
self.fields = fields
6535
self.time = time
66-
return self
6736
}
6837

6938
/// Creates Line Protocol from Data Point.
@@ -73,7 +42,7 @@ extension InfluxDBClient {
7342
/// - defaultTags: default tags for Point.
7443
/// - Returns: Line Protocol
7544
public func toLineProtocol(precision: TimestampPrecision = defaultTimestampPrecision,
76-
defaultTags: [String: String?]? = nil) throws -> String? {
45+
defaultTags: [String: String]? = nil) throws -> String? {
7746
let meas = escapeKey(measurement, false)
7847
let tags = escapeTags(defaultTags)
7948
let fields = try escapeFields()
@@ -117,8 +86,8 @@ extension InfluxDBClient {
11786
return self
11887
}
11988

120-
internal func evaluate() -> [String: String?] {
121-
let map: [String: String?] = tags.mapValues { value in
89+
internal func evaluate() -> [String: String] {
90+
let map: [String: String] = tags.compactMapValues { value in
12291
if let value = value, value.starts(with: "${env.") {
12392
let start = value.index(value.startIndex, offsetBy: 6)
12493
let end = value.index(value.endIndex, offsetBy: -1)
@@ -134,7 +103,7 @@ extension InfluxDBClient {
134103

135104
extension InfluxDBClient.Point {
136105
/// Possible value types of Field
137-
public enum FieldValue {
106+
public enum FieldValue: Sendable {
138107
/// Support for Int8
139108
init(_ value: Int8) {
140109
self = .int(Int(value))
@@ -185,7 +154,7 @@ extension InfluxDBClient.Point {
185154
}
186155

187156
/// Possible value types of Field
188-
public enum TimestampValue: CustomStringConvertible {
157+
public enum TimestampValue: CustomStringConvertible, Sendable {
189158
// The number of ticks since the UNIX epoch. The value has to be specified with correct precision.
190159
case interval(Int, InfluxDBClient.TimestampPrecision = InfluxDBClient.defaultTimestampPrecision)
191160
// The date timestamp.
@@ -205,29 +174,22 @@ extension InfluxDBClient.Point {
205174
extension InfluxDBClient.Point {
206175
/// Tuple definition for construct `Point`.
207176
public typealias Tuple = (measurement: String,
208-
tags: [String?: String?]?,
209-
fields: [String?: InfluxDBClient.Point.FieldValue?],
177+
tags: [String: String]?,
178+
fields: [String: InfluxDBClient.Point.FieldValue],
210179
time: InfluxDBClient.Point.TimestampValue?)
211180
/// Create a new Point from Tuple.
212181
///
213182
/// - Parameters:
214183
/// - tuple: the tuple with keys: `measurement`, `tags`, `fields` and `time`
215184
/// - precision: the data point precision
216185
/// - Returns: created Point
217-
public class func fromTuple(_ tuple: Tuple) -> InfluxDBClient.Point {
218-
let point = InfluxDBClient.Point(tuple.measurement)
219-
if let tags = tuple.tags {
220-
for tag in tags {
221-
point.addTag(key: tag.0, value: tag.1)
222-
}
223-
}
224-
for field in tuple.fields {
225-
point.addField(key: field.0, value: field.1)
226-
}
227-
if let time = tuple.time {
228-
point.time(time: time)
229-
}
230-
return point
186+
public static func fromTuple(_ tuple: Tuple) -> InfluxDBClient.Point {
187+
.init(
188+
tuple.measurement,
189+
tags: tuple.tags ?? [:],
190+
fields: tuple.fields,
191+
time: tuple.time
192+
)
231193
}
232194
}
233195

@@ -261,7 +223,7 @@ extension InfluxDBClient.Point {
261223
}
262224
}
263225

264-
private func escapeTags(_ defaultTags: [String: String?]?) -> String {
226+
private func escapeTags(_ defaultTags: [String: String]?) -> String {
265227
tags
266228
.merging(defaultTags ?? [:]) { current, _ in
267229
current
@@ -272,11 +234,11 @@ extension InfluxDBClient.Point {
272234
guard !keyValue.key.isEmpty else {
273235
return
274236
}
275-
if let value = keyValue.value, !value.isEmpty {
237+
if !keyValue.value.isEmpty {
276238
result.append(",")
277239
result.append(escapeKey(keyValue.key))
278240
result.append("=")
279-
result.append(escapeKey(value))
241+
result.append(escapeKey(keyValue.value))
280242
}
281243
}
282244
}
@@ -288,10 +250,7 @@ extension InfluxDBClient.Point {
288250
if keyValue.key.isEmpty {
289251
return
290252
}
291-
guard let value = keyValue.value else {
292-
return
293-
}
294-
if let escaped = try escapeValue(value) {
253+
if let escaped = try escapeValue(keyValue.value) {
295254
// key
296255
result.append(escapeKey(keyValue.key))
297256
// key=
@@ -386,3 +345,86 @@ extension InfluxDBClient.Point {
386345
return " \(sinceEpoch)"
387346
}
388347
}
348+
349+
extension InfluxDBClient.Point {
350+
/// Adds or replaces a tag value for this point.
351+
///
352+
/// - Parameters:
353+
/// - key: the tag name
354+
/// - value: the tag value
355+
/// - Returns: self
356+
@_disfavoredOverload
357+
@available(*, deprecated, message: "Pass tags to Point.init or use the tags property")
358+
public func addTag(key: String?, value: String?) -> Self {
359+
var point = self
360+
if let key = key {
361+
point.tags[key] = value
362+
}
363+
return point
364+
}
365+
366+
/// Adds or replaces a tag value for this point.
367+
///
368+
/// - Parameters:
369+
/// - key: the tag name
370+
/// - value: the tag value
371+
/// - Returns: self
372+
@available(*, deprecated, message: "Pass tags to Point.init or use the tags property")
373+
public mutating func addTag(key: String?, value: String?) {
374+
if let key = key {
375+
tags[key] = value
376+
}
377+
}
378+
379+
/// Adds or replaces a field value for this point.
380+
///
381+
/// - Parameters:
382+
/// - key: the field name
383+
/// - value: the field value
384+
/// - Returns: self
385+
@_disfavoredOverload
386+
@available(*, deprecated, message: "Pass fields to Point.init or use the fields property")
387+
public func addField(key: String?, value: FieldValue?) -> Self {
388+
var point = self
389+
if let key = key {
390+
point.fields[key] = value
391+
}
392+
return point
393+
}
394+
395+
/// Adds or replaces a field value for this point.
396+
///
397+
/// - Parameters:
398+
/// - key: the field name
399+
/// - value: the field value
400+
/// - Returns: self
401+
@available(*, deprecated, message: "Pass fields to Point.init or use the fields property")
402+
public mutating func addField(key: String?, value: FieldValue?) {
403+
if let key = key {
404+
fields[key] = value
405+
}
406+
}
407+
408+
/// Updates the timestamp for the point.
409+
///
410+
/// - Parameters:
411+
/// - time: the timestamp. It can be `Int` or `Date`.
412+
/// - Returns: self
413+
@_disfavoredOverload
414+
@available(*, deprecated, message: "Pass time to Point.init or use the time property")
415+
public func time(time: TimestampValue) -> Self {
416+
var point = self
417+
point.time = time
418+
return point
419+
}
420+
421+
/// Updates the timestamp for the point.
422+
///
423+
/// - Parameters:
424+
/// - time: the timestamp. It can be `Int` or `Date`.
425+
/// - Returns: self
426+
@available(*, deprecated, message: "Pass time to Point.init or use the time property")
427+
public mutating func time(time: TimestampValue) {
428+
self.time = time
429+
}
430+
}

Sources/InfluxDBSwift/WriteAPI.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ public struct WriteAPI {
801801

802802
private func toLineProtocol(precision: InfluxDBClient.TimestampPrecision,
803803
any: Any,
804-
defaultTags: [String: String?]?,
804+
defaultTags: [String: String]?,
805805
lps: inout [String]) throws {
806806
// To avoid: "Could not cast value of type 'InfluxDBSwift.InfluxDBClient.Point' to 'Foundation.NSObject'."
807807
// on Linux - see:
@@ -831,7 +831,7 @@ public struct WriteAPI {
831831

832832
private func toLineProtocol(precision: InfluxDBClient.TimestampPrecision,
833833
point: InfluxDBClient.Point,
834-
defaultTags: [String: String?]?,
834+
defaultTags: [String: String]?,
835835
lps: inout [String]) throws {
836836
if let lineProtocol = try point.toLineProtocol(precision: precision, defaultTags: defaultTags) {
837837
return toLineProtocol(

0 commit comments

Comments
 (0)