From 313e3224dd42ba1a6f03416cede7a22db0bb8fe8 Mon Sep 17 00:00:00 2001 From: Daniel Jilg Date: Mon, 11 Dec 2023 22:15:24 +0100 Subject: [PATCH] Add CustomQuery.stableHashValue property --- Package.resolved | 11 ++- Package.swift | 6 +- .../ChartDefinitionDTO.swift | 2 +- .../DataTransferObjects/DTOs/Aggregate.swift | 2 +- Sources/DataTransferObjects/DTOs/DTOv2.swift | 8 +-- .../DTOs/InsightCalculationResult.swift | 2 +- .../DTOs/InsightData.swift | 2 +- .../DTOs/KafkaSignalStructure.swift | 2 +- .../DTOs/LexiconSignalDTO.swift | 2 +- .../DTOs/OrganizationDTO.swift | 2 +- .../DTOs/RegistrationRequestBody.swift | 2 +- .../DataTransferObjects/DTOs/UserDTO.swift | 2 +- .../Extensions/String+CamelCase.swift | 2 +- .../Query/Aggregator.swift | 58 ++++++++-------- .../Query/CustomQuery+CompileDown.swift | 6 +- .../Query/CustomQuery.swift | 67 +++++++++++-------- .../Query/Datasource.swift | 10 +-- .../Query/DimensionSpec.swift | 4 +- .../Query/ExtractionFunction.swift | 8 +-- .../DataTransferObjects/Query/Filter.swift | 20 +++--- .../Query/NamedFilter.swift | 2 +- .../Query/PostAggregator.swift | 54 +++++++-------- .../Query/QueryTimeInterval.swift | 6 +- .../Query/RelativeTimeInterval.swift | 2 +- .../Query/TopNMetricSpec.swift | 6 +- .../CustomQuery+Experiment.swift | 20 +++--- .../QueryGeneration/CustomQuery+Funnel.swift | 7 +- .../RetentionQueryGenerator.swift | 8 +-- .../QueryGeneration/SQLQueryConversion.swift | 6 +- .../QueryResult/QueryResult.swift | 6 +- Sources/DataTransferObjects/UUID+Empty.swift | 2 +- .../ChartDefinitionTests.swift | 4 +- .../EncodingDecodingTests.swift | 23 +++---- .../RelativeDateTests.swift | 2 +- .../CompileDownTests.swift | 18 ++--- .../ExperimentQueryGenerationTests.swift | 10 +-- .../FunnelQueryGenerationTests.swift | 22 +++--- .../RetentionQueryGenerationTests.swift | 18 ++--- .../SQLQueryConversionTests.swift | 2 +- Tests/QueryResultTests/QueryResultTests.swift | 8 +-- Tests/QueryTests/AggregatorTests.swift | 7 +- Tests/QueryTests/CustomQueryTests.swift | 21 +++--- Tests/QueryTests/HashingTests.swift | 17 +++++ Tests/QueryTests/PostAggregatorTests.swift | 37 +++++----- Tests/QueryTests/RetentionQueryTests.swift | 22 +++--- Tests/QueryTests/TopNQueryTests.swift | 2 +- 46 files changed, 292 insertions(+), 258 deletions(-) diff --git a/Package.resolved b/Package.resolved index f710428..a38a9eb 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,9 +1,18 @@ { "object": { "pins": [ + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "60f13f60c4d093691934dc6cfdf5f508ada1f894", + "version": "2.6.0" + } + }, { "package": "DateOperations", - "repositoryURL": "git@github.com:TelemetryDeck/SwiftDateOperations.git", + "repositoryURL": "https://github.com/TelemetryDeck/SwiftDateOperations.git", "state": { "branch": null, "revision": "9baf08c95057688deffe1f14dfcc871ba2ef38d2", diff --git a/Package.swift b/Package.swift index 30baf52..5032390 100644 --- a/Package.swift +++ b/Package.swift @@ -20,13 +20,17 @@ let package = Package( // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), .package(url: "https://github.com/TelemetryDeck/SwiftDateOperations.git", from: "1.0.3"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "DataTransferObjects", - dependencies: [.product(name: "DateOperations", package: "SwiftDateOperations")] + dependencies: [ + .product(name: "DateOperations", package: "SwiftDateOperations"), + .product(name: "Crypto", package: "swift-crypto"), + ] ), .testTarget( name: "DataTransferObjectsTests", diff --git a/Sources/DataTransferObjects/ChartDefinitionDTOs/ChartDefinitionDTO.swift b/Sources/DataTransferObjects/ChartDefinitionDTOs/ChartDefinitionDTO.swift index 13bca2a..da489e7 100644 --- a/Sources/DataTransferObjects/ChartDefinitionDTOs/ChartDefinitionDTO.swift +++ b/Sources/DataTransferObjects/ChartDefinitionDTOs/ChartDefinitionDTO.swift @@ -1,5 +1,5 @@ // -// File.swift +// ChartDefinitionDTO.swift // // // Created by Daniel Jilg on 10.11.21. diff --git a/Sources/DataTransferObjects/DTOs/Aggregate.swift b/Sources/DataTransferObjects/DTOs/Aggregate.swift index ad7a3d7..9cfa0c1 100644 --- a/Sources/DataTransferObjects/DTOs/Aggregate.swift +++ b/Sources/DataTransferObjects/DTOs/Aggregate.swift @@ -1,5 +1,5 @@ // -// File.swift +// Aggregate.swift // // // Created by Daniel Jilg on 15.04.21. diff --git a/Sources/DataTransferObjects/DTOs/DTOv2.swift b/Sources/DataTransferObjects/DTOs/DTOv2.swift index ad84dd6..b3d2738 100644 --- a/Sources/DataTransferObjects/DTOs/DTOv2.swift +++ b/Sources/DataTransferObjects/DTOs/DTOv2.swift @@ -1,5 +1,5 @@ // -// InsightDTOs.swift +// DTOv2.swift // InsightDTOs // // Created by Daniel Jilg on 17.08.21. @@ -204,10 +204,10 @@ public enum DTOv2 { case app case website } - + /// If true, the app should display demo content instead of public var showExampleData: Bool? - + /// What colors should charts be in? /// /// This should be formatted as @@ -660,7 +660,7 @@ public extension DTOv2.Insight { intervals: [], granularity: .all, aggregations: [ - .longSum(.init(type: .longSum, name: "total_usage", fieldName: "count")) + .longSum(.init(type: .longSum, name: "total_usage", fieldName: "count")), ] ) diff --git a/Sources/DataTransferObjects/DTOs/InsightCalculationResult.swift b/Sources/DataTransferObjects/DTOs/InsightCalculationResult.swift index ef04e09..f723840 100644 --- a/Sources/DataTransferObjects/DTOs/InsightCalculationResult.swift +++ b/Sources/DataTransferObjects/DTOs/InsightCalculationResult.swift @@ -1,5 +1,5 @@ // -// File.swift +// InsightCalculationResult.swift // // // Created by Daniel Jilg on 09.04.21. diff --git a/Sources/DataTransferObjects/DTOs/InsightData.swift b/Sources/DataTransferObjects/DTOs/InsightData.swift index d05ef57..f73fc35 100644 --- a/Sources/DataTransferObjects/DTOs/InsightData.swift +++ b/Sources/DataTransferObjects/DTOs/InsightData.swift @@ -1,5 +1,5 @@ // -// File.swift +// InsightData.swift // // // Created by Daniel Jilg on 09.04.21. diff --git a/Sources/DataTransferObjects/DTOs/KafkaSignalStructure.swift b/Sources/DataTransferObjects/DTOs/KafkaSignalStructure.swift index 18b2ebd..ec13b93 100644 --- a/Sources/DataTransferObjects/DTOs/KafkaSignalStructure.swift +++ b/Sources/DataTransferObjects/DTOs/KafkaSignalStructure.swift @@ -1,5 +1,5 @@ // -// File.swift +// KafkaSignalStructure.swift // // // Created by Daniel Jilg on 05.06.21. diff --git a/Sources/DataTransferObjects/DTOs/LexiconSignalDTO.swift b/Sources/DataTransferObjects/DTOs/LexiconSignalDTO.swift index 6c1fc73..75467ab 100644 --- a/Sources/DataTransferObjects/DTOs/LexiconSignalDTO.swift +++ b/Sources/DataTransferObjects/DTOs/LexiconSignalDTO.swift @@ -1,5 +1,5 @@ // -// File.swift +// LexiconSignalDTO.swift // // // Created by Daniel Jilg on 12.05.21. diff --git a/Sources/DataTransferObjects/DTOs/OrganizationDTO.swift b/Sources/DataTransferObjects/DTOs/OrganizationDTO.swift index f1486d2..983e0ec 100644 --- a/Sources/DataTransferObjects/DTOs/OrganizationDTO.swift +++ b/Sources/DataTransferObjects/DTOs/OrganizationDTO.swift @@ -1,5 +1,5 @@ // -// File.swift +// OrganizationDTO.swift // // // Created by Daniel Jilg on 09.04.21. diff --git a/Sources/DataTransferObjects/DTOs/RegistrationRequestBody.swift b/Sources/DataTransferObjects/DTOs/RegistrationRequestBody.swift index 9c6683d..26fff52 100644 --- a/Sources/DataTransferObjects/DTOs/RegistrationRequestBody.swift +++ b/Sources/DataTransferObjects/DTOs/RegistrationRequestBody.swift @@ -1,5 +1,5 @@ // -// File.swift +// RegistrationRequestBody.swift // // // Created by Daniel Jilg on 14.05.21. diff --git a/Sources/DataTransferObjects/DTOs/UserDTO.swift b/Sources/DataTransferObjects/DTOs/UserDTO.swift index 660538a..5fb3788 100644 --- a/Sources/DataTransferObjects/DTOs/UserDTO.swift +++ b/Sources/DataTransferObjects/DTOs/UserDTO.swift @@ -1,5 +1,5 @@ // -// File.swift +// UserDTO.swift // // // Created by Daniel Jilg on 09.04.21. diff --git a/Sources/DataTransferObjects/Extensions/String+CamelCase.swift b/Sources/DataTransferObjects/Extensions/String+CamelCase.swift index af0f982..044443f 100644 --- a/Sources/DataTransferObjects/Extensions/String+CamelCase.swift +++ b/Sources/DataTransferObjects/Extensions/String+CamelCase.swift @@ -11,7 +11,7 @@ public extension String { var camelCaseToWords: String { unicodeScalars.reduce("") { if CharacterSet.uppercaseLetters.contains($1) { - return ($0 + " " + String($1)) + return $0 + " " + String($1) } return $0 + String($1) diff --git a/Sources/DataTransferObjects/Query/Aggregator.swift b/Sources/DataTransferObjects/Query/Aggregator.swift index c92501c..a527632 100644 --- a/Sources/DataTransferObjects/Query/Aggregator.swift +++ b/Sources/DataTransferObjects/Query/Aggregator.swift @@ -132,57 +132,57 @@ public indirect enum Aggregator: Codable, Hashable, Equatable { switch type { case "count": - self = .count(try CountAggregator(from: decoder)) + self = try .count(CountAggregator(from: decoder)) case "cardinality": - self = .cardinality(try CardinalityAggregator(from: decoder)) + self = try .cardinality(CardinalityAggregator(from: decoder)) case "longSum": - self = .longSum(try GenericAggregator(from: decoder)) + self = try .longSum(GenericAggregator(from: decoder)) case "doubleSum": - self = .doubleSum(try GenericAggregator(from: decoder)) + self = try .doubleSum(GenericAggregator(from: decoder)) case "floatSum": - self = .floatSum(try GenericAggregator(from: decoder)) + self = try .floatSum(GenericAggregator(from: decoder)) case "doubleMin": - self = .doubleMin(try GenericAggregator(from: decoder)) + self = try .doubleMin(GenericAggregator(from: decoder)) case "doubleMax": - self = .doubleMax(try GenericAggregator(from: decoder)) + self = try .doubleMax(GenericAggregator(from: decoder)) case "floatMin": - self = .floatMin(try GenericAggregator(from: decoder)) + self = try .floatMin(GenericAggregator(from: decoder)) case "floatMax": - self = .floatMax(try GenericAggregator(from: decoder)) + self = try .floatMax(GenericAggregator(from: decoder)) case "longMin": - self = .longMin(try GenericAggregator(from: decoder)) + self = try .longMin(GenericAggregator(from: decoder)) case "longMax": - self = .longMax(try GenericAggregator(from: decoder)) + self = try .longMax(GenericAggregator(from: decoder)) case "doubleMean": - self = .doubleMean(try GenericAggregator(from: decoder)) + self = try .doubleMean(GenericAggregator(from: decoder)) case "doubleFirst": - self = .doubleFirst(try GenericTimeColumnAggregator(from: decoder)) + self = try .doubleFirst(GenericTimeColumnAggregator(from: decoder)) case "doubleLast": - self = .doubleLast(try GenericTimeColumnAggregator(from: decoder)) + self = try .doubleLast(GenericTimeColumnAggregator(from: decoder)) case "floatFirst": - self = .floatFirst(try GenericTimeColumnAggregator(from: decoder)) + self = try .floatFirst(GenericTimeColumnAggregator(from: decoder)) case "floatLast": - self = .floatLast(try GenericTimeColumnAggregator(from: decoder)) + self = try .floatLast(GenericTimeColumnAggregator(from: decoder)) case "longFirst": - self = .longFirst(try GenericTimeColumnAggregator(from: decoder)) + self = try .longFirst(GenericTimeColumnAggregator(from: decoder)) case "longLast": - self = .longLast(try GenericTimeColumnAggregator(from: decoder)) + self = try .longLast(GenericTimeColumnAggregator(from: decoder)) case "stringFirst": - self = .stringFirst(try GenericTimeColumnAggregator(from: decoder)) + self = try .stringFirst(GenericTimeColumnAggregator(from: decoder)) case "stringLast": - self = .stringLast(try GenericTimeColumnAggregator(from: decoder)) + self = try .stringLast(GenericTimeColumnAggregator(from: decoder)) case "doubleAny": - self = .doubleAny(try GenericAggregator(from: decoder)) + self = try .doubleAny(GenericAggregator(from: decoder)) case "floatAny": - self = .floatAny(try GenericAggregator(from: decoder)) + self = try .floatAny(GenericAggregator(from: decoder)) case "longAny": - self = .longAny(try GenericAggregator(from: decoder)) + self = try .longAny(GenericAggregator(from: decoder)) case "stringAny": - self = .stringAny(try GenericAggregator(from: decoder)) + self = try .stringAny(GenericAggregator(from: decoder)) case "thetaSketch": - self = .thetaSketch(try GenericAggregator(from: decoder)) + self = try .thetaSketch(GenericAggregator(from: decoder)) case "filtered": - self = .filtered(try FilteredAggregator(from: decoder)) + self = try .filtered(FilteredAggregator(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) @@ -277,7 +277,7 @@ public indirect enum Aggregator: Codable, Hashable, Equatable { public struct CountAggregator: Codable, Hashable { public init(name: String) { - self.type = .count + type = .count self.name = name } @@ -290,7 +290,7 @@ public struct CountAggregator: Codable, Hashable { /// Calcluate the cardinality of a dimension (deprecated) public struct CardinalityAggregator: Codable, Hashable { public init(name: String, fields: [String], byRow: Bool = false, round: Bool = true) { - self.type = .cardinality + type = .cardinality self.name = name self.fields = fields self.byRow = byRow @@ -382,7 +382,7 @@ public enum AggregatorType: String, Codable, Hashable { /// Note: If only the filtered results are required, consider putting the filter on the query itself, which will be much faster since it does not require scanning all the data. public struct FilteredAggregator: Codable, Hashable { public init(filter: Filter, aggregator: Aggregator) { - self.type = .filtered + type = .filtered self.filter = filter self.aggregator = aggregator } diff --git a/Sources/DataTransferObjects/Query/CustomQuery+CompileDown.swift b/Sources/DataTransferObjects/Query/CustomQuery+CompileDown.swift index a731e7a..88efc47 100644 --- a/Sources/DataTransferObjects/Query/CustomQuery+CompileDown.swift +++ b/Sources/DataTransferObjects/Query/CustomQuery+CompileDown.swift @@ -101,12 +101,12 @@ extension CustomQuery { switch baseFilters { case .thisOrganization: guard let organizationAppIDs = organizationAppIDs else { throw QueryGenerationError.keyMissing(reason: "Missing organization app IDs") } - query.filter = query.filter && (try appIDFilter(for: organizationAppIDs)) && testModeFilter(for: query) + query.filter = try query.filter && appIDFilter(for: organizationAppIDs) && testModeFilter(for: query) return query case .thisApp: guard let appID = query.appID else { throw QueryGenerationError.keyMissing(reason: "Missing key 'appID'") } - query.filter = query.filter && (try appIDFilter(for: [appID])) && testModeFilter(for: query) + query.filter = try query.filter && appIDFilter(for: [appID]) && testModeFilter(for: query) return query case .exampleData: @@ -121,7 +121,7 @@ extension CustomQuery { /// Returns a filter according to the query objects `testMode` property. static func testModeFilter(for query: CustomQuery) -> Filter { - return Filter.selector(.init(dimension: "isTestMode", value: "\(query.testMode ?? false ? "true" : "false")")) + Filter.selector(.init(dimension: "isTestMode", value: "\(query.testMode ?? false ? "true" : "false")")) } // Given a list of app UUIDs, generates a Filter object that restricts a query to only apps with either of the given IDs diff --git a/Sources/DataTransferObjects/Query/CustomQuery.swift b/Sources/DataTransferObjects/Query/CustomQuery.swift index 92484b0..af4316e 100644 --- a/Sources/DataTransferObjects/Query/CustomQuery.swift +++ b/Sources/DataTransferObjects/Query/CustomQuery.swift @@ -1,3 +1,4 @@ +import Crypto import Foundation /// Custom JSON based query @@ -148,21 +149,31 @@ public struct CustomQuery: Codable, Hashable, Equatable { /// Only for groupBy Queries: A list of dimensions to do the groupBy over, if queryType is groupBy public var dimensions: [DimensionSpec]? - + /// Only for funnel Queries: A list of filters that form the steps of the funnel public var steps: [NamedFilter]? - + /// Only for experiment Queries: The control cohort for the experiment public var sample1: NamedFilter? - + /// Only for experiment Queries: The experiment cohort for the experiment public var sample2: NamedFilter? - + /// Only for experiment Queries: A named filter that defines the successful cohort in the experiment. /// /// Will be intersected with cohort 1 for success 1 and cohort 2 for success 2 public var successCriterion: NamedFilter? + public var stableHashValue: String { + // We've tried various hashing functions here and they are just not stable. + // So instead, let's convert to JSON and hash that. + + guard let jsonData = try? JSONEncoder.telemetryEncoder.encode(self) else { return "nothashed" } + let digest = SHA256.hash(data: jsonData) + + return digest.compactMap { String(format: "%02x", $0) }.joined() + } + public func hash(into hasher: inout Hasher) { hasher.combine(queryType) hasher.combine(compilationStatus) @@ -197,34 +208,34 @@ public struct CustomQuery: Codable, Hashable, Equatable { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CustomQuery.CodingKeys.self) - self.queryType = try container.decode(CustomQuery.QueryType.self, forKey: CustomQuery.CodingKeys.queryType) - self.compilationStatus = try container.decodeIfPresent(CompilationStatus.self, forKey: CustomQuery.CodingKeys.compilationStatus) - self.restrictions = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.restrictions) - self.dataSource = try container.decodeIfPresent(DataSource.self, forKey: CustomQuery.CodingKeys.dataSource) - self.descending = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.descending) - self.baseFilters = try container.decodeIfPresent(BaseFilters.self, forKey: CustomQuery.CodingKeys.baseFilters) - self.testMode = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.testMode) - self.filter = try container.decodeIfPresent(Filter.self, forKey: CustomQuery.CodingKeys.filter) - self.appID = try container.decodeIfPresent(UUID.self, forKey: CustomQuery.CodingKeys.appID) - self.relativeIntervals = try container.decodeIfPresent([RelativeTimeInterval].self, forKey: CustomQuery.CodingKeys.relativeIntervals) - self.granularity = try container.decode(QueryGranularity.self, forKey: CustomQuery.CodingKeys.granularity) - self.aggregations = try container.decodeIfPresent([Aggregator].self, forKey: CustomQuery.CodingKeys.aggregations) - self.postAggregations = try container.decodeIfPresent([PostAggregator].self, forKey: CustomQuery.CodingKeys.postAggregations) - self.limit = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.limit) - self.context = try container.decodeIfPresent(QueryContext.self, forKey: CustomQuery.CodingKeys.context) - self.threshold = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.threshold) - self.dimension = try container.decodeIfPresent(DimensionSpec.self, forKey: CustomQuery.CodingKeys.dimension) - self.metric = try container.decodeIfPresent(TopNMetricSpec.self, forKey: CustomQuery.CodingKeys.metric) - self.dimensions = try container.decodeIfPresent([DimensionSpec].self, forKey: CustomQuery.CodingKeys.dimensions) - self.steps = try container.decodeIfPresent([NamedFilter].self, forKey: CustomQuery.CodingKeys.steps) - self.sample1 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample1) - self.sample2 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample2) - self.successCriterion = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.successCriterion) + queryType = try container.decode(CustomQuery.QueryType.self, forKey: CustomQuery.CodingKeys.queryType) + compilationStatus = try container.decodeIfPresent(CompilationStatus.self, forKey: CustomQuery.CodingKeys.compilationStatus) + restrictions = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.restrictions) + dataSource = try container.decodeIfPresent(DataSource.self, forKey: CustomQuery.CodingKeys.dataSource) + descending = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.descending) + baseFilters = try container.decodeIfPresent(BaseFilters.self, forKey: CustomQuery.CodingKeys.baseFilters) + testMode = try container.decodeIfPresent(Bool.self, forKey: CustomQuery.CodingKeys.testMode) + filter = try container.decodeIfPresent(Filter.self, forKey: CustomQuery.CodingKeys.filter) + appID = try container.decodeIfPresent(UUID.self, forKey: CustomQuery.CodingKeys.appID) + relativeIntervals = try container.decodeIfPresent([RelativeTimeInterval].self, forKey: CustomQuery.CodingKeys.relativeIntervals) + granularity = try container.decode(QueryGranularity.self, forKey: CustomQuery.CodingKeys.granularity) + aggregations = try container.decodeIfPresent([Aggregator].self, forKey: CustomQuery.CodingKeys.aggregations) + postAggregations = try container.decodeIfPresent([PostAggregator].self, forKey: CustomQuery.CodingKeys.postAggregations) + limit = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.limit) + context = try container.decodeIfPresent(QueryContext.self, forKey: CustomQuery.CodingKeys.context) + threshold = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.threshold) + dimension = try container.decodeIfPresent(DimensionSpec.self, forKey: CustomQuery.CodingKeys.dimension) + metric = try container.decodeIfPresent(TopNMetricSpec.self, forKey: CustomQuery.CodingKeys.metric) + dimensions = try container.decodeIfPresent([DimensionSpec].self, forKey: CustomQuery.CodingKeys.dimensions) + steps = try container.decodeIfPresent([NamedFilter].self, forKey: CustomQuery.CodingKeys.steps) + sample1 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample1) + sample2 = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.sample2) + successCriterion = try container.decodeIfPresent(NamedFilter.self, forKey: CustomQuery.CodingKeys.successCriterion) if let intervals = try? container.decode(QueryTimeIntervalsContainer.self, forKey: CustomQuery.CodingKeys.intervals) { self.intervals = intervals.intervals } else { - self.intervals = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.intervals) + intervals = try container.decodeIfPresent([QueryTimeInterval].self, forKey: CustomQuery.CodingKeys.intervals) } } } diff --git a/Sources/DataTransferObjects/Query/Datasource.swift b/Sources/DataTransferObjects/Query/Datasource.swift index 3f4c1f8..1581ce9 100644 --- a/Sources/DataTransferObjects/Query/Datasource.swift +++ b/Sources/DataTransferObjects/Query/Datasource.swift @@ -7,7 +7,7 @@ public struct DataSource: Codable, Hashable, Equatable { } public init(_ name: String) { - self.type = .table + type = .table self.name = name } @@ -38,15 +38,15 @@ public struct DataSource: Codable, Hashable, Equatable { } public func encode(to encoder: Encoder) throws { - if self.type == .table { + if type == .table { var container = encoder.singleValueContainer() - try container.encode(self.name) + try container.encode(name) return } var container: KeyedEncodingContainer = encoder.container(keyedBy: DataSource.CodingKeys.self) - try container.encode(self.type, forKey: DataSource.CodingKeys.type) - try container.encode(self.name, forKey: DataSource.CodingKeys.name) + try container.encode(type, forKey: DataSource.CodingKeys.type) + try container.encode(name, forKey: DataSource.CodingKeys.name) } } diff --git a/Sources/DataTransferObjects/Query/DimensionSpec.swift b/Sources/DataTransferObjects/Query/DimensionSpec.swift index 9217193..c5c9dbe 100644 --- a/Sources/DataTransferObjects/Query/DimensionSpec.swift +++ b/Sources/DataTransferObjects/Query/DimensionSpec.swift @@ -20,9 +20,9 @@ public indirect enum DimensionSpec: Codable, Equatable, Hashable { switch type { case "default": - self = .default(try DefaultDimensionSpec(from: decoder)) + self = try .default(DefaultDimensionSpec(from: decoder)) case "extraction": - self = .extraction(try ExtractionDimensionSpec(from: decoder)) + self = try .extraction(ExtractionDimensionSpec(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) } diff --git a/Sources/DataTransferObjects/Query/ExtractionFunction.swift b/Sources/DataTransferObjects/Query/ExtractionFunction.swift index 0a7ec28..c9dc634 100644 --- a/Sources/DataTransferObjects/Query/ExtractionFunction.swift +++ b/Sources/DataTransferObjects/Query/ExtractionFunction.swift @@ -16,11 +16,11 @@ public indirect enum ExtractionFunction: Codable, Equatable, Hashable { switch type { case "regex": - self = .regex(try RegularExpressionExtractionFunction(from: decoder)) + self = try .regex(RegularExpressionExtractionFunction(from: decoder)) case "lookup": - self = .inlineLookup(try InlineLookupExtractionFunction(from: decoder)) + self = try .inlineLookup(InlineLookupExtractionFunction(from: decoder)) case "registeredLookup": - self = .registeredLookup(try RegisteredLookupExtractionFunction(from: decoder)) + self = try .registeredLookup(RegisteredLookupExtractionFunction(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) } @@ -83,7 +83,7 @@ public struct RegularExpressionExtractionFunction: Codable, Equatable, Hashable /// It is illegal to set retainMissingValue = true and also specify a replaceMissingValueWith. public struct InlineLookupExtractionFunction: Codable, Equatable, Hashable { public init(lookupMap: [String: String], retainMissingValue: Bool = true, injective: Bool = true, replaceMissingValueWith: String? = nil) { - self.lookup = Lookup(map: lookupMap) + lookup = Lookup(map: lookupMap) self.retainMissingValue = retainMissingValue self.injective = injective self.replaceMissingValueWith = replaceMissingValueWith diff --git a/Sources/DataTransferObjects/Query/Filter.swift b/Sources/DataTransferObjects/Query/Filter.swift index 3f834d6..e53b650 100644 --- a/Sources/DataTransferObjects/Query/Filter.swift +++ b/Sources/DataTransferObjects/Query/Filter.swift @@ -112,24 +112,24 @@ public indirect enum Filter: Codable, Hashable, Equatable { switch type { case "selector": - self = .selector(try FilterSelector(from: decoder)) + self = try .selector(FilterSelector(from: decoder)) case "columnComparison": - self = .columnComparison(try FilterColumnComparison(from: decoder)) + self = try .columnComparison(FilterColumnComparison(from: decoder)) case "interval": - self = .interval(try FilterInterval(from: decoder)) + self = try .interval(FilterInterval(from: decoder)) case "regex": - self = .regex(try FilterRegex(from: decoder)) + self = try .regex(FilterRegex(from: decoder)) case "and": - self = .and(try FilterExpression(from: decoder)) + self = try .and(FilterExpression(from: decoder)) case "or": - self = .or(try FilterExpression(from: decoder)) + self = try .or(FilterExpression(from: decoder)) case "not": - self = .not(try FilterNot(from: decoder)) + self = try .not(FilterNot(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) } } - + /// Empty Filter that basically catches everthing public static var empty: Filter { Filter.not(.init(field: .selector(.init(dimension: "appID", value: "0")))) @@ -164,7 +164,7 @@ public indirect enum Filter: Codable, Hashable, Equatable { } public static func && (lhs: Filter, rhs: Filter) -> Filter { - return Filter.and(.init(fields: [lhs, rhs])) + Filter.and(.init(fields: [lhs, rhs])) } public static func && (lhs: Filter, rhs: Filter?) -> Filter { @@ -178,6 +178,6 @@ public indirect enum Filter: Codable, Hashable, Equatable { } public static func || (lhs: Filter, rhs: Filter) -> Filter { - return Filter.or(.init(fields: [lhs, rhs])) + Filter.or(.init(fields: [lhs, rhs])) } } diff --git a/Sources/DataTransferObjects/Query/NamedFilter.swift b/Sources/DataTransferObjects/Query/NamedFilter.swift index 570cb23..377884c 100644 --- a/Sources/DataTransferObjects/Query/NamedFilter.swift +++ b/Sources/DataTransferObjects/Query/NamedFilter.swift @@ -9,7 +9,7 @@ public struct NamedFilter: Codable, Hashable, Equatable { self.filter = filter self.name = name } - + public let filter: Filter? public let name: String } diff --git a/Sources/DataTransferObjects/Query/PostAggregator.swift b/Sources/DataTransferObjects/Query/PostAggregator.swift index e2cf8d2..d2a45cc 100644 --- a/Sources/DataTransferObjects/Query/PostAggregator.swift +++ b/Sources/DataTransferObjects/Query/PostAggregator.swift @@ -23,7 +23,7 @@ public indirect enum PostAggregator: Codable, Hashable, Equatable { // From DataSketches ThetaSketches case thetaSketchEstimate(ThetaSketchEstimatePostAggregator) case thetaSketchSetOp(ThetaSketchSetOpPostAggregator) - + // From druid-stats case zscore2sample(ZScore2SamplePostAggregator) case pvalue2tailedZtest(PValue2TailedZTestPostAggregator) @@ -41,35 +41,35 @@ public indirect enum PostAggregator: Codable, Hashable, Equatable { switch type { case "arithmetic": - self = .arithmetic(try ArithmetricPostAggregator(from: decoder)) + self = try .arithmetic(ArithmetricPostAggregator(from: decoder)) case "fieldAccess": - self = .fieldAccess(try FieldAccessPostAggregator(from: decoder)) + self = try .fieldAccess(FieldAccessPostAggregator(from: decoder)) case "finalizingFieldAccess": - self = .finalizingFieldAccess(try FieldAccessPostAggregator(from: decoder)) + self = try .finalizingFieldAccess(FieldAccessPostAggregator(from: decoder)) case "constant": - self = .constant(try ConstantPostAggregator(from: decoder)) + self = try .constant(ConstantPostAggregator(from: decoder)) case "doubleGreatest": - self = .doubleGreatest(try GreatestLeastPostAggregator(from: decoder)) + self = try .doubleGreatest(GreatestLeastPostAggregator(from: decoder)) case "longGreatest": - self = .longGreatest(try GreatestLeastPostAggregator(from: decoder)) + self = try .longGreatest(GreatestLeastPostAggregator(from: decoder)) case "doubleMax": - self = .doubleMax(try GreatestLeastPostAggregator(from: decoder)) + self = try .doubleMax(GreatestLeastPostAggregator(from: decoder)) case "doubleLeast": - self = .doubleLeast(try GreatestLeastPostAggregator(from: decoder)) + self = try .doubleLeast(GreatestLeastPostAggregator(from: decoder)) case "longLeast": - self = .longLeast(try GreatestLeastPostAggregator(from: decoder)) + self = try .longLeast(GreatestLeastPostAggregator(from: decoder)) case "hyperUniqueCardinality": - self = .hyperUniqueCardinality(try HyperUniqueCardinalityPostAggregator(from: decoder)) + self = try .hyperUniqueCardinality(HyperUniqueCardinalityPostAggregator(from: decoder)) case "expression": - self = .expression(try ExpressionPostAggregator(from: decoder)) + self = try .expression(ExpressionPostAggregator(from: decoder)) case "thetaSketchEstimate": - self = .thetaSketchEstimate(try ThetaSketchEstimatePostAggregator(from: decoder)) + self = try .thetaSketchEstimate(ThetaSketchEstimatePostAggregator(from: decoder)) case "thetaSketchSetOp": - self = .thetaSketchSetOp(try ThetaSketchSetOpPostAggregator(from: decoder)) + self = try .thetaSketchSetOp(ThetaSketchSetOpPostAggregator(from: decoder)) case "zscore2sample": - self = .zscore2sample(try ZScore2SamplePostAggregator(from: decoder)) + self = try .zscore2sample(ZScore2SamplePostAggregator(from: decoder)) case "pvalue2tailedZtest": - self = .pvalue2tailedZtest(try PValue2TailedZTestPostAggregator(from: decoder)) + self = try .pvalue2tailedZtest(PValue2TailedZTestPostAggregator(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type: \(type)", underlyingError: nil)) } @@ -169,9 +169,9 @@ public enum PostAggregatorOrdering: String, Codable, Hashable { /// - numericFirst ordering always returns finite values first, followed by NaN, and infinite values last. public struct ArithmetricPostAggregator: Codable, Hashable { public init(name: String, function: MathematicalFunction, fields: [PostAggregator], ordering: PostAggregatorOrdering? = nil) { - self.type = .arithmetic + type = .arithmetic self.name = name - self.fn = function + fn = function self.fields = fields self.ordering = ordering } @@ -222,7 +222,7 @@ public struct FieldAccessPostAggregator: Codable, Hashable { /// The constant post-aggregator always returns the specified value. public struct ConstantPostAggregator: Codable, Hashable { public init(name: String, value: Double) { - self.type = .constant + type = .constant self.name = name self.value = value } @@ -255,7 +255,7 @@ public struct GreatestLeastPostAggregator: Codable, Hashable { /// see https://druid.apache.org/docs/latest/misc/math-expr.html public struct ExpressionPostAggregator: Codable, Hashable { public init(name: String, expression: String, ordering: PostAggregatorOrdering? = nil) { - self.type = .expression + type = .expression self.name = name self.expression = expression self.ordering = ordering @@ -274,7 +274,7 @@ public struct ExpressionPostAggregator: Codable, Hashable { /// The hyperUniqueCardinality post aggregator is used to wrap a hyperUnique object such that it can be used in post aggregations. public struct HyperUniqueCardinalityPostAggregator: Codable, Hashable { public init(name: String? = nil, fieldName: String) { - self.type = .hyperUniqueCardinality + type = .hyperUniqueCardinality self.name = name self.fieldName = fieldName } @@ -287,7 +287,7 @@ public struct HyperUniqueCardinalityPostAggregator: Codable, Hashable { /// "field" : public struct ThetaSketchEstimatePostAggregator: Codable, Hashable { public init(name: String? = nil, field: PostAggregator) { - self.type = .thetaSketchEstimate + type = .thetaSketchEstimate self.name = name self.field = field } @@ -299,7 +299,7 @@ public struct ThetaSketchEstimatePostAggregator: Codable, Hashable { public struct ThetaSketchSetOpPostAggregator: Codable, Hashable { public init(name: String? = nil, func: ThetaSketchSetOpPostAggregator.SketchOperation, fields: [PostAggregator]) { - self.type = .thetaSketchSetOp + type = .thetaSketchSetOp self.name = name self.func = `func` self.fields = fields @@ -328,14 +328,14 @@ public struct ThetaSketchSetOpPostAggregator: Codable, Hashable { /// @see https://medium.com/paypal-tech/democratizing-experimentation-data-for-product-innovations-8b6e1cf40c27#DemocratizingExperimentationScience-Druid public struct ZScore2SamplePostAggregator: Codable, Hashable { public init(name: String, sample1Size: PostAggregator, successCount1: PostAggregator, sample2Size: PostAggregator, successCount2: PostAggregator) { - self.type = .zscore2sample + type = .zscore2sample self.name = name self.sample1Size = sample1Size self.successCount1 = successCount1 self.sample2Size = sample2Size self.successCount2 = successCount2 } - + public let type: PostAggregatorType public let name: String public let sample1Size: PostAggregator @@ -352,11 +352,11 @@ public struct ZScore2SamplePostAggregator: Codable, Hashable { /// @see https://medium.com/paypal-tech/democratizing-experimentation-data-for-product-innovations-8b6e1cf40c27#DemocratizingExperimentationScience-Druid public struct PValue2TailedZTestPostAggregator: Codable, Hashable { public init(name: String, zScore: PostAggregator) { - self.type = .pvalue2tailedZtest + type = .pvalue2tailedZtest self.name = name self.zScore = zScore } - + public let type: PostAggregatorType public let name: String public let zScore: PostAggregator diff --git a/Sources/DataTransferObjects/Query/QueryTimeInterval.swift b/Sources/DataTransferObjects/Query/QueryTimeInterval.swift index e43e9fe..96fef87 100644 --- a/Sources/DataTransferObjects/Query/QueryTimeInterval.swift +++ b/Sources/DataTransferObjects/Query/QueryTimeInterval.swift @@ -51,10 +51,10 @@ public struct QueryTimeInterval: Codable, Hashable, Equatable, Comparable { } public init(dateInterval: DateInterval) { - self.beginningDate = dateInterval.start - self.endDate = dateInterval.end + beginningDate = dateInterval.start + endDate = dateInterval.end } - + public static func < (lhs: QueryTimeInterval, rhs: QueryTimeInterval) -> Bool { if lhs.beginningDate == rhs.beginningDate { return lhs.endDate < rhs.endDate } return lhs.beginningDate < rhs.beginningDate diff --git a/Sources/DataTransferObjects/Query/RelativeTimeInterval.swift b/Sources/DataTransferObjects/Query/RelativeTimeInterval.swift index 4fc303d..0a6403a 100644 --- a/Sources/DataTransferObjects/Query/RelativeTimeInterval.swift +++ b/Sources/DataTransferObjects/Query/RelativeTimeInterval.swift @@ -85,7 +85,7 @@ public extension Date { public extension QueryTimeInterval { static func from(relativeTimeInterval: RelativeTimeInterval) -> QueryTimeInterval { - return QueryTimeInterval( + QueryTimeInterval( beginningDate: Date.from(relativeDate: relativeTimeInterval.beginningDate), endDate: Date.from(relativeDate: relativeTimeInterval.endDate) ) diff --git a/Sources/DataTransferObjects/Query/TopNMetricSpec.swift b/Sources/DataTransferObjects/Query/TopNMetricSpec.swift index 3f38459..e38613c 100644 --- a/Sources/DataTransferObjects/Query/TopNMetricSpec.swift +++ b/Sources/DataTransferObjects/Query/TopNMetricSpec.swift @@ -16,11 +16,11 @@ public indirect enum TopNMetricSpec: Codable, Equatable, Hashable { switch type { case "numeric": - self = .numeric(try NumericTopNMetricSpec(from: decoder)) + self = try .numeric(NumericTopNMetricSpec(from: decoder)) case "dimension": - self = .dimension(try DimensionTopNMetricSpec(from: decoder)) + self = try .dimension(DimensionTopNMetricSpec(from: decoder)) case "inverted": - self = .inverted(try InvertedTopNMetricSpec(from: decoder)) + self = try .inverted(InvertedTopNMetricSpec(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) } diff --git a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Experiment.swift b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Experiment.swift index bc88454..2295d38 100644 --- a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Experiment.swift +++ b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Experiment.swift @@ -1,15 +1,15 @@ extension CustomQuery { func precompiledExperimentQuery() throws -> CustomQuery { var query = self - + guard let sample1 = sample1 else { throw QueryGenerationError.keyMissing(reason: "Missing key 'sample1'") } guard let sample2 = sample2 else { throw QueryGenerationError.keyMissing(reason: "Missing key 'sample2'") } guard let successCriterion = successCriterion else { throw QueryGenerationError.keyMissing(reason: "Missing key 'successCriterion'") } - + // Generate Filter Statement // In theory, we could pre-filter here by combining all filtered aggregations with an "or" filter. Which // might bring a bit of a perfmance benefit. - + // Generate Aggregations var aggregations = [Aggregator]() for combined in zip( @@ -27,11 +27,11 @@ extension CustomQuery { )) ) } - + aggregations.append(.filtered(.init( filter: .or(.init(fields: [ sample1.filter ?? Filter.empty, - sample2.filter ?? Filter.empty + sample2.filter ?? Filter.empty, ])), aggregator: .thetaSketch(.init( type: .thetaSketch, @@ -39,7 +39,7 @@ extension CustomQuery { fieldName: "clientUser" )) ))) - + // Generate Post-Agregations let postAggregations: [PostAggregator] = [ .thetaSketchEstimate(.init( @@ -54,7 +54,7 @@ extension CustomQuery { .fieldAccess(.init( type: .fieldAccess, fieldName: "success" - )) + )), ] )) )), @@ -70,7 +70,7 @@ extension CustomQuery { .fieldAccess(.init( type: .fieldAccess, fieldName: "success" - )) + )), ] )) )), @@ -96,9 +96,9 @@ extension CustomQuery { .pvalue2tailedZtest(.init( name: "pvalue", zScore: .fieldAccess(.init(type: .fieldAccess, fieldName: "zscore")) - )) + )), ] - + // Combine query query.queryType = .groupBy // query.filter = queryFilter diff --git a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift index c9bcca2..3633b87 100644 --- a/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift +++ b/Sources/DataTransferObjects/QueryGeneration/CustomQuery+Funnel.swift @@ -5,14 +5,13 @@ extension CustomQuery { guard let steps = steps else { throw QueryGenerationError.keyMissing(reason: "Missing key 'steps'") } // Generate Filter Statement - let stepsFilters = Filter.or(.init(fields: steps.compactMap { $0.filter })) + let stepsFilters = Filter.or(.init(fields: steps.compactMap(\.filter))) let queryFilter = filter && stepsFilters // Generate Aggregations let aggregationNamePrefix = "_funnel_step_" var aggregations = [Aggregator]() - - + for (index, step) in steps.enumerated() { aggregations.append(.filtered(.init( filter: step.filter ?? Filter.empty, @@ -61,6 +60,6 @@ extension CustomQuery { private extension Array { subscript(safe index: Index, default defaultValue: Element) -> Element { - return indices.contains(index) ? self[index] : defaultValue + indices.contains(index) ? self[index] : defaultValue } } diff --git a/Sources/DataTransferObjects/QueryGeneration/RetentionQueryGenerator.swift b/Sources/DataTransferObjects/QueryGeneration/RetentionQueryGenerator.swift index b907b52..9cf14e6 100644 --- a/Sources/DataTransferObjects/QueryGeneration/RetentionQueryGenerator.swift +++ b/Sources/DataTransferObjects/QueryGeneration/RetentionQueryGenerator.swift @@ -42,7 +42,7 @@ public enum RetentionQueryGenerator { dataSource: "telemetry-signals", filter: .and(.init(fields: [ .selector(.init(dimension: "appID", value: appID)), - .selector(.init(dimension: "isTestMode", value: testMode ? "true" : "false")) + .selector(.init(dimension: "isTestMode", value: testMode ? "true" : "false")), ])), intervals: [QueryTimeInterval(beginningDate: beginDate, endDate: endDate)], granularity: .all, @@ -89,7 +89,7 @@ public enum RetentionQueryGenerator { } static func aggregator(for interval: DateInterval) -> Aggregator { - return .filtered(.init( + .filtered(.init( filter: .interval(.init( dimension: "__time", intervals: [.init(dateInterval: interval)] @@ -104,13 +104,13 @@ public enum RetentionQueryGenerator { } static func postAggregatorBetween(interval1: DateInterval, interval2: DateInterval) -> PostAggregator { - return .thetaSketchEstimate(.init( + .thetaSketchEstimate(.init( name: "retention_\(title(for: interval1))_\(title(for: interval2))", field: .thetaSketchSetOp(.init( func: .intersect, fields: [ .fieldAccess(.init(type: .fieldAccess, fieldName: "_\(title(for: interval1))")), - .fieldAccess(.init(type: .fieldAccess, fieldName: "_\(title(for: interval2))")) + .fieldAccess(.init(type: .fieldAccess, fieldName: "_\(title(for: interval2))")), ] ) ) diff --git a/Sources/DataTransferObjects/QueryGeneration/SQLQueryConversion.swift b/Sources/DataTransferObjects/QueryGeneration/SQLQueryConversion.swift index d989487..c3a3620 100644 --- a/Sources/DataTransferObjects/QueryGeneration/SQLQueryConversion.swift +++ b/Sources/DataTransferObjects/QueryGeneration/SQLQueryConversion.swift @@ -15,7 +15,7 @@ public struct SQLQueryConversionResponseItem: Codable, Equatable { } public init(plan: String) { - self.PLAN = plan + PLAN = plan } public let PLAN: String @@ -29,7 +29,7 @@ public struct SQLQueryConversionResponseItem: Codable, Equatable { )) } - let planItems = try JSONDecoder.telemetryDecoder.decode([PlanContainerItem].self, from: planData ) + let planItems = try JSONDecoder.telemetryDecoder.decode([PlanContainerItem].self, from: planData) guard let firstPlanItem = planItems.first else { throw DecodingError.dataCorrupted(.init( @@ -41,7 +41,7 @@ public struct SQLQueryConversionResponseItem: Codable, Equatable { var query = firstPlanItem.query - query.dataSource = DataSource.init("telemetry-signals") + query.dataSource = DataSource("telemetry-signals") query.context = nil query.intervals = nil diff --git a/Sources/DataTransferObjects/QueryResult/QueryResult.swift b/Sources/DataTransferObjects/QueryResult/QueryResult.swift index 0c4a286..87156f4 100644 --- a/Sources/DataTransferObjects/QueryResult/QueryResult.swift +++ b/Sources/DataTransferObjects/QueryResult/QueryResult.swift @@ -15,11 +15,11 @@ public enum QueryResult: Codable, Hashable, Equatable { switch type { case "timeSeriesResult": - self = .timeSeries(try TimeSeriesQueryResult(from: decoder)) + self = try .timeSeries(TimeSeriesQueryResult(from: decoder)) case "topNResult": - self = .topN(try TopNQueryResult(from: decoder)) + self = try .topN(TopNQueryResult(from: decoder)) case "groupByResult": - self = .groupBy(try GroupByQueryResult(from: decoder)) + self = try .groupBy(GroupByQueryResult(from: decoder)) default: throw EncodingError.invalidValue("Invalid type", .init(codingPath: [CodingKeys.type], debugDescription: "Invalid Type", underlyingError: nil)) } diff --git a/Sources/DataTransferObjects/UUID+Empty.swift b/Sources/DataTransferObjects/UUID+Empty.swift index 38d0623..902370d 100644 --- a/Sources/DataTransferObjects/UUID+Empty.swift +++ b/Sources/DataTransferObjects/UUID+Empty.swift @@ -1,5 +1,5 @@ // -// File.swift +// UUID+Empty.swift // File // // Created by Daniel Jilg on 17.08.21. diff --git a/Tests/DataTransferObjectsTests/ChartDefinitionTests.swift b/Tests/DataTransferObjectsTests/ChartDefinitionTests.swift index bbded74..82649fb 100644 --- a/Tests/DataTransferObjectsTests/ChartDefinitionTests.swift +++ b/Tests/DataTransferObjectsTests/ChartDefinitionTests.swift @@ -24,7 +24,7 @@ final class ChartDefinitionTests: XCTestCase { {"columns":[],"x":"x"} """ - XCTAssertEqual(String(data: try JSONEncoder.telemetryEncoder.encode(exampleDataSection), encoding: .utf8)!, expectedResult) + XCTAssertEqual(try String(data: JSONEncoder.telemetryEncoder.encode(exampleDataSection), encoding: .utf8)!, expectedResult) } func testColumnEncoding() throws { @@ -34,7 +34,7 @@ final class ChartDefinitionTests: XCTestCase { ["data1","12","31",null,"42"] """ - XCTAssertEqual(String(data: try JSONEncoder.telemetryEncoder.encode(exampleColumn), encoding: .utf8)!, expectedResult) + XCTAssertEqual(try String(data: JSONEncoder.telemetryEncoder.encode(exampleColumn), encoding: .utf8)!, expectedResult) } func testColumnDecoding() throws { diff --git a/Tests/DataTransferObjectsTests/EncodingDecodingTests.swift b/Tests/DataTransferObjectsTests/EncodingDecodingTests.swift index 0a4ea6c..b3f04ea 100644 --- a/Tests/DataTransferObjectsTests/EncodingDecodingTests.swift +++ b/Tests/DataTransferObjectsTests/EncodingDecodingTests.swift @@ -4,7 +4,7 @@ import XCTest final class EncodingDecodingTests: XCTestCase { func testAppSettingsEncoding() throws { let input = DTOv2.AppSettings(displayMode: .app) - + let output = try JSONEncoder.telemetryEncoder.encode(input) let expectedOutput = """ @@ -14,10 +14,10 @@ final class EncodingDecodingTests: XCTestCase { } """ .filter { !$0.isWhitespace } - + XCTAssertEqual(expectedOutput, String(data: output, encoding: .utf8)!) } - + func testAppSettingsDecoding() throws { let input = """ { @@ -25,12 +25,12 @@ final class EncodingDecodingTests: XCTestCase { } """ .filter { !$0.isWhitespace } - + let output = try JSONDecoder.telemetryDecoder.decode(DTOv2.AppSettings.self, from: input.data(using: .utf8)!) - + XCTAssertEqual(output.displayMode, .website) } - + func testAppSettingsDecodingMore() throws { let input = """ { @@ -39,12 +39,12 @@ final class EncodingDecodingTests: XCTestCase { } """ .filter { !$0.isWhitespace } - + let output = try JSONDecoder.telemetryDecoder.decode(DTOv2.AppSettings.self, from: input.data(using: .utf8)!) - + XCTAssertEqual(output.displayMode, .website) } - + func testAppSettingsDecodingMoreMore() throws { let input = """ { @@ -54,10 +54,9 @@ final class EncodingDecodingTests: XCTestCase { } """ .filter { !$0.isWhitespace } - + let output = try JSONDecoder.telemetryDecoder.decode(DTOv2.AppSettings.self, from: input.data(using: .utf8)!) - + XCTAssertEqual(output.displayMode, .website) } - } diff --git a/Tests/DataTransferObjectsTests/RelativeDateTests.swift b/Tests/DataTransferObjectsTests/RelativeDateTests.swift index 7698d94..c1318d9 100644 --- a/Tests/DataTransferObjectsTests/RelativeDateTests.swift +++ b/Tests/DataTransferObjectsTests/RelativeDateTests.swift @@ -103,7 +103,7 @@ final class RelativeDateTests: XCTestCase { RelativeTimeInterval( beginningDate: RelativeDate(.beginning, of: .month, adding: -1), endDate: RelativeDate(.end, of: .month, adding: 0) - ) + ), ] let expectedOutput = """ diff --git a/Tests/QueryGenerationTests/CompileDownTests.swift b/Tests/QueryGenerationTests/CompileDownTests.swift index a2dd0c1..05972ba 100644 --- a/Tests/QueryGenerationTests/CompileDownTests.swift +++ b/Tests/QueryGenerationTests/CompileDownTests.swift @@ -3,7 +3,7 @@ import XCTest final class CompileDownTests: XCTestCase { let relativeIntervals = [ - RelativeTimeInterval(beginningDate: .init(.beginning, of: .month, adding: 0), endDate: .init(.end, of: .month, adding: 0)) + RelativeTimeInterval(beginningDate: .init(.beginning, of: .month, adding: 0), endDate: .init(.end, of: .month, adding: 0)), ] let appID1 = UUID() @@ -14,7 +14,7 @@ final class CompileDownTests: XCTestCase { .init(filter: .selector(.init(dimension: "type", value: "appLaunchedRegularly")), name: "Regular Launch"), .init(filter: .selector(.init(dimension: "type", value: "dataEntered")), name: "Data Entered"), .init(filter: .selector(.init(dimension: "type", value: "paywallSeen")), name: "Paywall Presented"), - .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion") + .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion"), ] let query = CustomQuery(queryType: .funnel, relativeIntervals: relativeIntervals, granularity: .all, steps: steps) @@ -48,10 +48,10 @@ final class CompileDownTests: XCTestCase { .selector(.init( dimension: "appID", value: appID2.uuidString - )) + )), ] )), - .selector(.init(dimension: "isTestMode", value: "false")) + .selector(.init(dimension: "isTestMode", value: "false")), ] )) ) @@ -71,7 +71,7 @@ final class CompileDownTests: XCTestCase { precompiledQuery.filter, .and(.init(fields: [ .selector(.init(dimension: "appID", value: appID.uuidString)), - .selector(.init(dimension: "isTestMode", value: "false")) + .selector(.init(dimension: "isTestMode", value: "false")), ])) ) } @@ -84,7 +84,7 @@ final class CompileDownTests: XCTestCase { precompiledQuery.filter, .and(.init(fields: [ .selector(.init(dimension: "appID", value: "B97579B6-FFB8-4AC5-AAA7-DA5796CC5DCE")), - .selector(.init(dimension: "isTestMode", value: "false")) + .selector(.init(dimension: "isTestMode", value: "false")), ])) ) } @@ -151,7 +151,7 @@ final class CompileDownTests: XCTestCase { func testRestrictions() throws { let intervals: [QueryTimeInterval] = [ - .init(beginningDate: Date(iso8601String: "2023-04-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-05-31T00:00:00.000Z")!) + .init(beginningDate: Date(iso8601String: "2023-04-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-05-31T00:00:00.000Z")!), ] let restrictions: [QueryTimeInterval] = [ @@ -162,7 +162,7 @@ final class CompileDownTests: XCTestCase { .init(beginningDate: Date(iso8601String: "2023-05-14T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-05-31T00:00:00.000Z")!), // This restriction should be included because it applies partially - .init(beginningDate: Date(iso8601String: "2023-03-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-04-02T00:00:00.000Z")!) + .init(beginningDate: Date(iso8601String: "2023-03-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-04-02T00:00:00.000Z")!), ] let query = CustomQuery(queryType: .timeseries, restrictions: restrictions, intervals: intervals, granularity: .all) @@ -171,7 +171,7 @@ final class CompileDownTests: XCTestCase { XCTAssertEqual(compiledQuery.restrictions, [ .init(beginningDate: Date(iso8601String: "2023-03-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-04-02T00:00:00.000Z")!), - .init(beginningDate: Date(iso8601String: "2023-05-14T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-05-31T00:00:00.000Z")!) + .init(beginningDate: Date(iso8601String: "2023-05-14T00:00:00.000Z")!, endDate: Date(iso8601String: "2023-05-31T00:00:00.000Z")!), ]) } } diff --git a/Tests/QueryGenerationTests/ExperimentQueryGenerationTests.swift b/Tests/QueryGenerationTests/ExperimentQueryGenerationTests.swift index 69fb451..889e6f3 100644 --- a/Tests/QueryGenerationTests/ExperimentQueryGenerationTests.swift +++ b/Tests/QueryGenerationTests/ExperimentQueryGenerationTests.swift @@ -23,7 +23,7 @@ final class ExperimentQueryGenerationTests: XCTestCase { )), .selector(.init( dimension: "isTestMode", value: "false" - )) + )), ])), granularity: .all, aggregations: [ @@ -69,7 +69,7 @@ final class ExperimentQueryGenerationTests: XCTestCase { fieldName: "clientUser" ) ) - )) + )), ], postAggregations: [ .thetaSketchEstimate(.init( @@ -84,7 +84,7 @@ final class ExperimentQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "success" - )) + )), ] )) )), @@ -100,7 +100,7 @@ final class ExperimentQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "success" - )) + )), ] )) )), @@ -126,7 +126,7 @@ final class ExperimentQueryGenerationTests: XCTestCase { .pvalue2tailedZtest(.init( name: "pvalue", zScore: .fieldAccess(.init(type: .fieldAccess, fieldName: "zscore")) - )) + )), ] ) diff --git a/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift b/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift index 7ffb32f..8ba8be7 100644 --- a/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift +++ b/Tests/QueryGenerationTests/FunnelQueryGenerationTests.swift @@ -6,7 +6,7 @@ final class FunnelQueryGenerationTests: XCTestCase { .init(filter: .selector(.init(dimension: "type", value: "appLaunchedRegularly")), name: "Regular Launch"), .init(filter: .selector(.init(dimension: "type", value: "dataEntered")), name: "Data Entered"), .init(filter: .selector(.init(dimension: "type", value: "paywallSeen")), name: "Paywall Presented"), - .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion") + .init(filter: .selector(.init(dimension: "type", value: "conversion")), name: "Conversion"), ] let tinyQuery = CustomQuery( @@ -17,7 +17,7 @@ final class FunnelQueryGenerationTests: XCTestCase { .selector(.init(dimension: "type", value: "appLaunchedRegularly")), .selector(.init(dimension: "type", value: "dataEntered")), .selector(.init(dimension: "type", value: "paywallSeen")), - .selector(.init(dimension: "type", value: "conversion")) + .selector(.init(dimension: "type", value: "conversion")), ])), granularity: .all, @@ -61,7 +61,7 @@ final class FunnelQueryGenerationTests: XCTestCase { fieldName: "clientUser" ) ) - )) + )), ], postAggregations: [ .thetaSketchEstimate(.init( @@ -83,7 +83,7 @@ final class FunnelQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_funnel_step_1" - )) + )), ] )) )), @@ -103,7 +103,7 @@ final class FunnelQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_funnel_step_2" - )) + )), ] )) )), @@ -127,10 +127,10 @@ final class FunnelQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_funnel_step_3" - )) + )), ] )) - )) + )), ] ) @@ -155,8 +155,8 @@ final class FunnelQueryGenerationTests: XCTestCase { .selector(.init(dimension: "type", value: "appLaunchedRegularly")), .selector(.init(dimension: "type", value: "dataEntered")), .selector(.init(dimension: "type", value: "paywallSeen")), - .selector(.init(dimension: "type", value: "conversion")) - ])) + .selector(.init(dimension: "type", value: "conversion")), + ])), ])) XCTAssertEqual(expectedFilter, generatedTinyQuery.filter) @@ -169,7 +169,7 @@ final class FunnelQueryGenerationTests: XCTestCase { RelativeTimeInterval( beginningDate: RelativeDate(.beginning, of: .month, adding: -1), endDate: RelativeDate(.end, of: .month, adding: 0) - ) + ), ] let startingQuery = CustomQuery(queryType: .funnel, relativeIntervals: relativeTimeIntervals, granularity: .all, steps: steps) @@ -185,7 +185,7 @@ final class FunnelQueryGenerationTests: XCTestCase { let startingQuery = CustomQuery(queryType: .funnel, granularity: .all, steps: steps) let generatedTinyQuery = try startingQuery.precompiledFunnelQuery() - print(String(data: try JSONEncoder.telemetryEncoder.encode(generatedTinyQuery), encoding: .utf8)!) + try print(String(data: JSONEncoder.telemetryEncoder.encode(generatedTinyQuery), encoding: .utf8)!) XCTAssertEqual(tinyQuery.filter, generatedTinyQuery.filter) XCTAssertEqual(tinyQuery.aggregations, generatedTinyQuery.aggregations) diff --git a/Tests/QueryGenerationTests/RetentionQueryGenerationTests.swift b/Tests/QueryGenerationTests/RetentionQueryGenerationTests.swift index 6ce8a69..65eec1b 100644 --- a/Tests/QueryGenerationTests/RetentionQueryGenerationTests.swift +++ b/Tests/QueryGenerationTests/RetentionQueryGenerationTests.swift @@ -16,13 +16,13 @@ final class RetentionQueryGenerationTests: XCTestCase { dataSource: "telemetry-signals", filter: .and(.init(fields: [ .selector(.init(dimension: "appID", value: "79167A27-EBBF-4012-9974-160624E5D07B")), - .selector(.init(dimension: "isTestMode", value: "false")) + .selector(.init(dimension: "isTestMode", value: "false")), ])), intervals: [ QueryTimeInterval( beginningDate: Date(iso8601String: "2022-08-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-09-30T00:00:00.000Z")! - ) + ), ], granularity: .all, aggregations: [ .filtered(.init( @@ -32,7 +32,7 @@ final class RetentionQueryGenerationTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-08-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-08-31T23:59:59.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -50,7 +50,7 @@ final class RetentionQueryGenerationTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-09-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-09-30T23:59:59.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -60,7 +60,7 @@ final class RetentionQueryGenerationTests: XCTestCase { fieldName: "clientUser" ) ) - )) + )), ], postAggregations: [ .thetaSketchEstimate(.init( @@ -75,7 +75,7 @@ final class RetentionQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_2022-08-01T00:00:00.000Z_2022-08-31T23:59:59.000Z" - )) + )), ] )) ) @@ -92,7 +92,7 @@ final class RetentionQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_2022-09-01T00:00:00.000Z_2022-09-30T23:59:59.000Z" - )) + )), ] )) ) @@ -109,7 +109,7 @@ final class RetentionQueryGenerationTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_2022-09-01T00:00:00.000Z_2022-09-30T23:59:59.000Z" - )) + )), ] )) ) @@ -154,7 +154,7 @@ final class RetentionQueryGenerationTests: XCTestCase { // fatalError() // } // } -// +// // let postAggregationNames = generatedTinyQuery.postAggregations!.map { postAgg in // switch postAgg { // case .thetaSketchEstimate(let thetaEstimateAgg): diff --git a/Tests/QueryGenerationTests/SQLQueryConversionTests.swift b/Tests/QueryGenerationTests/SQLQueryConversionTests.swift index d29682b..183e7f0 100644 --- a/Tests/QueryGenerationTests/SQLQueryConversionTests.swift +++ b/Tests/QueryGenerationTests/SQLQueryConversionTests.swift @@ -23,7 +23,7 @@ final class SQLQueryConversionTests: XCTestCase { } ] """ - .data(using: .utf8)! + .data(using: .utf8)! func testEncodingConversionRequest() throws { let input = SQLQueryConversionRequest(query: sqlQuery) diff --git a/Tests/QueryResultTests/QueryResultTests.swift b/Tests/QueryResultTests/QueryResultTests.swift index 173d193..79baffc 100644 --- a/Tests/QueryResultTests/QueryResultTests.swift +++ b/Tests/QueryResultTests/QueryResultTests.swift @@ -1,5 +1,5 @@ // -// DruidQueryResultTests.swift +// QueryResultTests.swift // // // Created by Daniel Jilg on 22.12.21. @@ -9,7 +9,7 @@ import DataTransferObjects import XCTest class QueryResultTests: XCTestCase { - let randomDate = Date(timeIntervalSinceReferenceDate: 656510400) // Thursday, October 21, 2021 2:00:00 PM GMT+02:00 + let randomDate = Date(timeIntervalSinceReferenceDate: 656_510_400) // Thursday, October 21, 2021 2:00:00 PM GMT+02:00 func testEncodingTimeSeries() throws { let exampleQueryResult = QueryResult.timeSeries( @@ -90,7 +90,7 @@ class QueryResultTests: XCTestCase { let decodedResult = try JSONDecoder.telemetryDecoder.decode(TimeSeriesQueryResultRow.self, from: exampleResult.data(using: .utf8)!) - XCTAssertEqual(decodedResult.result, ["d0": DoubleWrapper(1609459200000)]) + XCTAssertEqual(decodedResult.result, ["d0": DoubleWrapper(1_609_459_200_000)]) } func testDecodingInfinity() throws { @@ -148,5 +148,3 @@ class QueryResultTests: XCTestCase { XCTAssertEqual(String(data: encodedQueryResult, encoding: .utf8)!, expectedResult) } } - - diff --git a/Tests/QueryTests/AggregatorTests.swift b/Tests/QueryTests/AggregatorTests.swift index 7f537a2..c5add0d 100644 --- a/Tests/QueryTests/AggregatorTests.swift +++ b/Tests/QueryTests/AggregatorTests.swift @@ -94,8 +94,7 @@ final class AggregatorTests: XCTestCase { XCTAssertEqual(try JSONDecoder.telemetryDecoder.decode([Aggregator].self, from: exampleAggregatorsString.data(using: .utf8)!), exampleAggregators) - XCTAssertEqual(String(data: try JSONEncoder.telemetryEncoder.encode(exampleAggregators), encoding: .utf8)!, exampleAggregatorsString) - + XCTAssertEqual(try String(data: JSONEncoder.telemetryEncoder.encode(exampleAggregators), encoding: .utf8)!, exampleAggregatorsString) } func testFilteredAggregatorDecoding() throws { @@ -129,7 +128,7 @@ final class AggregatorTests: XCTestCase { dimension: "type", value: "InsightShown" ) - ) + ), ] ) ), @@ -141,7 +140,7 @@ final class AggregatorTests: XCTestCase { ) ) ) - ) + ), ]) } } diff --git a/Tests/QueryTests/CustomQueryTests.swift b/Tests/QueryTests/CustomQueryTests.swift index c740b1c..91aab6e 100644 --- a/Tests/QueryTests/CustomQueryTests.swift +++ b/Tests/QueryTests/CustomQueryTests.swift @@ -144,8 +144,7 @@ final class CustomQueryTests: XCTestCase { XCTAssertTrue(decodedOutput.replaceMissingValue) XCTAssertEqual(expectedOutput, decodedOutput) } - - + func testRegisteredLookupExtractionFunctionEncodingDefault() throws { let input = ExtractionFunction.registeredLookup(.init(lookup: "apps", retainMissingValue: true)) @@ -174,9 +173,9 @@ final class CustomQueryTests: XCTestCase { XCTAssertEqual(expectedOutput, decodedOutput) } - + func testInlineLookupExtractionFunctionEncodingDefault() throws { - let input = ExtractionFunction.inlineLookup(InlineLookupExtractionFunction.init(lookupMap: ["foo": "bar", "baz":"bat"])) + let input = ExtractionFunction.inlineLookup(InlineLookupExtractionFunction(lookupMap: ["foo": "bar", "baz": "bat"])) let expectedOutput = """ {"injective":true,"lookup":{"map":{"baz":"bat","foo":"bar"},"type":"map"},"retainMissingValue":true,"type":"lookup"} @@ -201,15 +200,15 @@ final class CustomQueryTests: XCTestCase { """ .data(using: .utf8)! - let expectedOutput = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz":"bat"])) + let expectedOutput = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz": "bat"])) let decodedOutput = try JSONDecoder.telemetryDecoder.decode(ExtractionFunction.self, from: input) XCTAssertEqual(expectedOutput, decodedOutput) } - + func testInlineLookupExtractionFunctionEncodingNonInjective() throws { - let input = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz":"bat"], retainMissingValue: false, injective: false, replaceMissingValueWith: "MISSING")) + let input = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz": "bat"], retainMissingValue: false, injective: false, replaceMissingValueWith: "MISSING")) let expectedOutput = """ {"injective":false,"lookup":{"map":{"baz":"bat","foo":"bar"},"type":"map"},"replaceMissingValueWith":"MISSING","retainMissingValue":false,"type":"lookup"} @@ -235,13 +234,13 @@ final class CustomQueryTests: XCTestCase { """ .data(using: .utf8)! - let expectedOutput = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz":"bat"], retainMissingValue: false, injective: false, replaceMissingValueWith: "MISSING")) + let expectedOutput = ExtractionFunction.inlineLookup(.init(lookupMap: ["foo": "bar", "baz": "bat"], retainMissingValue: false, injective: false, replaceMissingValueWith: "MISSING")) let decodedOutput = try JSONDecoder.telemetryDecoder.decode(ExtractionFunction.self, from: input) XCTAssertEqual(expectedOutput, decodedOutput) } - + func testRegisteredLookupDimensionDecoding() throws { let input = """ { @@ -256,9 +255,9 @@ final class CustomQueryTests: XCTestCase { } """ .data(using: .utf8)! - + let expectedOutput = DimensionSpec.extraction(.init(dimension: "appID", outputName: "App", extractionFn: .registeredLookup(.init(lookup: "apps", retainMissingValue: true)))) - + let decodedOutput = try JSONDecoder.telemetryDecoder.decode(DimensionSpec.self, from: input) XCTAssertEqual(expectedOutput, decodedOutput) diff --git a/Tests/QueryTests/HashingTests.swift b/Tests/QueryTests/HashingTests.swift index 1d49152..b40a8c6 100644 --- a/Tests/QueryTests/HashingTests.swift +++ b/Tests/QueryTests/HashingTests.swift @@ -209,4 +209,21 @@ class HashingTests: XCTestCase { XCTAssertNotEqual(query1.hashValue, query2.hashValue) } + + func testCustomQueryHashingStable() { + let exampleTopNQuery = CustomQuery( + queryType: .topN, + dataSource: "telemetry-signals", + intervals: [.init(beginningDate: Self.beginDate, endDate: Self.endDate)], + granularity: .all, + aggregations: [.count(.init(name: "count"))], + threshold: 10, + metric: .dimension(.init(ordering: .version)), + dimension: .default(.init(dimension: "appVersion", outputName: "appVersion")) + ) + + let hashValue = "\(exampleTopNQuery.stableHashValue)" + + XCTAssertEqual(hashValue, "58b597a17a48338e1c2d4799ed56548cc239265a246962388d0a17dbf7d2dc36") + } } diff --git a/Tests/QueryTests/PostAggregatorTests.swift b/Tests/QueryTests/PostAggregatorTests.swift index 87ef778..5d312ee 100644 --- a/Tests/QueryTests/PostAggregatorTests.swift +++ b/Tests/QueryTests/PostAggregatorTests.swift @@ -1,5 +1,5 @@ // -// File.swift +// PostAggregatorTests.swift // // // Created by Daniel Jilg on 22.09.22. @@ -48,10 +48,10 @@ final class PostAggregatorTests: XCTestCase { func: .intersect, fields: [ .fieldAccess(.init(type: .fieldAccess, fieldName: "appLaunchedByNotification_count")), - .fieldAccess(.init(type: .fieldAccess, fieldName: "dataEntered_count")) + .fieldAccess(.init(type: .fieldAccess, fieldName: "dataEntered_count")), ] )) - )) + )), ] ) } @@ -91,12 +91,12 @@ final class PostAggregatorTests: XCTestCase { name: "ratio", function: .division, fields: [ .fieldAccess(.init(type: .fieldAccess, name: "part", fieldName: "part")), - .fieldAccess(.init(type: .fieldAccess, name: "tot", fieldName: "tot")) + .fieldAccess(.init(type: .fieldAccess, name: "tot", fieldName: "tot")), ] )), - PostAggregator.constant(.init(name: "const", value: 100)) + PostAggregator.constant(.init(name: "const", value: 100)), ] - )) + )), ] ) } @@ -117,7 +117,7 @@ final class PostAggregatorTests: XCTestCase { XCTAssertEqual( decodedAggregators, [ - .expression(.init(name: "part_percentage", expression: "100*(part/tot)")) + .expression(.init(name: "part_percentage", expression: "100*(part/tot)")), ] ) } @@ -145,9 +145,9 @@ final class PostAggregatorTests: XCTestCase { function: .division, fields: [ .fieldAccess(.init(type: .fieldAccess, name: "tot", fieldName: "tot")), - .fieldAccess(.init(type: .fieldAccess, name: "rows", fieldName: "rows")) + .fieldAccess(.init(type: .fieldAccess, name: "rows", fieldName: "rows")), ] - )) + )), ]) } @@ -174,9 +174,9 @@ final class PostAggregatorTests: XCTestCase { function: .division, fields: [ .hyperUniqueCardinality(.init(fieldName: "unique_users")), - .fieldAccess(.init(type: .fieldAccess, name: "rows", fieldName: "rows")) + .fieldAccess(.init(type: .fieldAccess, name: "rows", fieldName: "rows")), ] - )) + )), ]) } @@ -204,8 +204,7 @@ final class PostAggregatorTests: XCTestCase { }] """ .filter { !$0.isWhitespace } - - + let postAggregators = [ PostAggregator.zscore2sample(.init( name: "zscore", @@ -225,16 +224,16 @@ final class PostAggregatorTests: XCTestCase { type: .finalizingFieldAccess, fieldName: "_cohort_1_success_0" )) - )) + )), ] let decodedAggregators = try JSONDecoder.telemetryDecoder.decode([PostAggregator].self, from: example.data(using: .utf8)!) XCTAssertEqual(decodedAggregators, postAggregators) - + let encodedAggregators = try JSONEncoder.telemetryEncoder.encode(postAggregators) XCTAssertEqual(String(data: encodedAggregators, encoding: .utf8), example) } - + func testPValueCodable() throws { let example = """ [{ @@ -247,17 +246,17 @@ final class PostAggregatorTests: XCTestCase { }] """ .filter { !$0.isWhitespace } - + let postAggregators = [ PostAggregator.pvalue2tailedZtest(.init( name: "pvalue", zScore: .fieldAccess(.init(type: .fieldAccess, fieldName: "zscore")) - )) + )), ] let decodedAggregators = try JSONDecoder.telemetryDecoder.decode([PostAggregator].self, from: example.data(using: .utf8)!) XCTAssertEqual(decodedAggregators, postAggregators) - + let encodedAggregators = try JSONEncoder.telemetryEncoder.encode(postAggregators) XCTAssertEqual(String(data: encodedAggregators, encoding: .utf8), example) } diff --git a/Tests/QueryTests/RetentionQueryTests.swift b/Tests/QueryTests/RetentionQueryTests.swift index f3c4440..9ef501b 100644 --- a/Tests/QueryTests/RetentionQueryTests.swift +++ b/Tests/QueryTests/RetentionQueryTests.swift @@ -1,5 +1,5 @@ // -// File.swift +// RetentionQueryTests.swift // // // Created by Daniel Jilg on 25.11.22. @@ -17,7 +17,7 @@ class RetentionQueryTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-08-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-09-01T00:00:00.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -35,7 +35,7 @@ class RetentionQueryTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-09-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-10-01T00:00:00.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -53,7 +53,7 @@ class RetentionQueryTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-10-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-11-01T00:00:00.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -71,7 +71,7 @@ class RetentionQueryTests: XCTestCase { .init( beginningDate: Date(iso8601String: "2022-11-01T00:00:00.000Z")!, endDate: Date(iso8601String: "2022-12-01T00:00:00.000Z")! - ) + ), ] )), aggregator: .thetaSketch( @@ -81,7 +81,7 @@ class RetentionQueryTests: XCTestCase { fieldName: "clientUser" ) ) - )) + )), ] let retentionQueryExample = CustomQuery( @@ -89,7 +89,7 @@ class RetentionQueryTests: XCTestCase { dataSource: "telemetry-signals", filter: .and(.init(fields: [ .selector(.init(dimension: "appID", value: "79167A27-EBBF-4012-9974-160624E5D07B")), - .selector(.init(dimension: "isTestMode", value: "false")) + .selector(.init(dimension: "isTestMode", value: "false")), ])), granularity: .all, aggregations: aggregations, @@ -106,7 +106,7 @@ class RetentionQueryTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_september_clientUser_count" - )) + )), ] )) ) @@ -124,7 +124,7 @@ class RetentionQueryTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_october_clientUser_count" - )) + )), ] )) ) @@ -142,11 +142,11 @@ class RetentionQueryTests: XCTestCase { .fieldAccess(.init( type: .fieldAccess, fieldName: "_november_clientUser_count" - )) + )), ] )) ) - ) + ), ] ) diff --git a/Tests/QueryTests/TopNQueryTests.swift b/Tests/QueryTests/TopNQueryTests.swift index 06ccdca..8c3a8a3 100644 --- a/Tests/QueryTests/TopNQueryTests.swift +++ b/Tests/QueryTests/TopNQueryTests.swift @@ -1,5 +1,5 @@ // -// DruidTopNQueryTests.swift +// TopNQueryTests.swift // // // Created by Daniel Jilg on 03.01.22.