From 32ae98c94dd00ac5c27f597d634ba18b0095c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Thu, 21 Nov 2024 22:20:28 +0100 Subject: [PATCH] Add `AccessDataSource.fetchObjects` that takes a fetchspec name bindings ... required in OGo. --- Sources/ZeeQL/Access/AccessDataSource.swift | 91 ++++++++++++++++++- .../ZeeQL/Access/AccessDataSourceError.swift | 12 ++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Sources/ZeeQL/Access/AccessDataSource.swift b/Sources/ZeeQL/Access/AccessDataSource.swift index fd4183a..b10c7a3 100644 --- a/Sources/ZeeQL/Access/AccessDataSource.swift +++ b/Sources/ZeeQL/Access/AccessDataSource.swift @@ -36,6 +36,8 @@ * top of an ``AdaptorChannel``. */ open class AccessDataSource : DataSource { + // TODO: Both DataSource and AccessDataSource should be protocols w/ PATs now, + // generics are good enough in Swift now. open var log : ZeeQLLogger = globalZeeQLLogger var _fsname : String? @@ -87,7 +89,7 @@ open class AccessDataSource : DataSource { open var entity : Entity? { fatalError("implement in subclass: \(#function)") } - + open func _primaryFetchObjects(_ fs: FetchSpecification, yield: ( Object ) throws -> Void) throws { @@ -100,6 +102,9 @@ open class AccessDataSource : DataSource { yield: ( GlobalID ) throws -> Void) throws { fatalError("implement in subclass: \(#function)") } + + + // MARK: - Fetch Convenience override open func fetchObjects(yield: ( Object ) -> Void) throws { // `iteratorForObjects` in GETobjects @@ -112,6 +117,58 @@ open class AccessDataSource : DataSource { try _primaryFetchGlobalIDs(try fetchSpecificationForFetch(), yield: yield) } + /** + * This method takes the name of a fetch specification. It looks up the fetch + * spec in the `Entity` associated with the datasource and then binds the + * spec with the given key/value pairs. + * + * Example: + * ```swift + * let persons = try ds.fetchObjects("myContacts", "contactId", 12345) + * ``` + * + * This will lookup the `FetchSpecification` named "myContacts" in + * the `Entity` of the datasource. It then calls + * `fetchSpecificationWithQualifierBindings()` + * and passes in the given key/value pair (contactId=12345). + * + * Finally the fetch will be performed using + * ``_primaryFetchObjects``. + * + * - Parameters: + * - fetchSpecificationName: The name of the fetch specification to use. + * - keysAndValues: The key/value pairs to apply as bindings. + * - Returns: The fetched objects. + */ + public func fetchObjects(_ fetchSpecificationName: String, + _ keysAndValues: Any...) throws -> [ Object ] + { + guard let findEntity = entity else { + // TBD: improve exception + log.error("did not find entity, cannot construct fetchspec"); + throw AccessDataSourceError.MissingEntity + } + + guard let fs = findEntity[fetchSpecification: fetchSpecificationName] else { + throw AccessDataSourceError + .DidNotFindFetchSpecification(name: fetchSpecificationName, + entity: findEntity) + } + + let binds = [ String: Any ].createArgs(keysAndValues) + var results = [ Object ]() + if !binds.isEmpty { + guard let fs = try fs.fetchSpecificiationWith(bindings: binds) else { + throw AccessDataSourceError + .CouldNotResolveBindings(fetchSpecification: fs, bindings: binds) + } + try _primaryFetchObjects(fs) { results.append($0) } + } + else { + try _primaryFetchObjects(fs) { results.append($0) } + } + return results + } // MARK: - Bindings @@ -130,6 +187,15 @@ open class AccessDataSource : DataSource { // MARK: - Fetch Specification + /** + * Takes the configured fetch specification and applies the auxiliary + * qualifier and qualifier bindings on it. + * + * This method always returns a copy of the fetch specification object, + * so callers are free to modify the result of this method. + * + * - Returns: A new fetch specification with bindings/qualifier applied. + */ func fetchSpecificationForFetch() throws -> FetchSpecification { /* copy fetchspec */ var fs : FetchSpecification @@ -165,7 +231,28 @@ open class AccessDataSource : DataSource { } return fs } - else { return fs } + + return fs } } + +fileprivate extension Dictionary where Key == String, Value == Any { + + /** + * This method creates a new `Dictionary` from a set of given array containing + * key/value arguments. + */ + static func createArgs(_ values: [ Any ]) -> Self { + guard !values.isEmpty else { return [:] } + var me = Self() + me.reserveCapacity(values.count / 2 + 1) + + for idx in stride(from: 0, to: values.count, by: 2) { + let anyKey = values[idx] + let value = values[idx + 1] + me[anyKey as? String ?? String(describing: anyKey)] = value + } + return me + } +} diff --git a/Sources/ZeeQL/Access/AccessDataSourceError.swift b/Sources/ZeeQL/Access/AccessDataSourceError.swift index 68b09d5..39f41f8 100644 --- a/Sources/ZeeQL/Access/AccessDataSourceError.swift +++ b/Sources/ZeeQL/Access/AccessDataSourceError.swift @@ -3,11 +3,13 @@ // ZeeQL // // Created by Helge Heß on 22.08.19. -// Copyright © 2019-2020 ZeeZide GmbH. All rights reserved. +// Copyright © 2019-2024 ZeeZide GmbH. All rights reserved. // -public enum AccessDataSourceError : Swift.Error { // cannot nest in generic - public enum ConstructionErrorReason { +public enum AccessDataSourceError: Swift.Error { + // cannot nest in generic + + public enum ConstructionErrorReason: Equatable { case missingEntity case bindingFailed case invalidPrimaryKey @@ -19,4 +21,8 @@ public enum AccessDataSourceError : Swift.Error { // cannot nest in generic case CountFetchReturnedNoResults case FetchReturnedMoreThanOneResult(fetchSpecification: FetchSpecification, firstObject: SwiftObject) + + case DidNotFindFetchSpecification(name: String, entity: Entity) + case CouldNotResolveBindings(fetchSpecification: FetchSpecification, + bindings: [ String : Any]) }