Skip to content

Commit

Permalink
Add AccessDataSource.fetchObjects that takes a fetchspec name bindings
Browse files Browse the repository at this point in the history
... required in OGo.
  • Loading branch information
helje5 committed Nov 21, 2024
1 parent c46d352 commit 32ae98c
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 5 deletions.
91 changes: 89 additions & 2 deletions Sources/ZeeQL/Access/AccessDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
* top of an ``AdaptorChannel``.
*/
open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
// 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?
Expand Down Expand Up @@ -87,7 +89,7 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
open var entity : Entity? {
fatalError("implement in subclass: \(#function)")
}

open func _primaryFetchObjects(_ fs: FetchSpecification,
yield: ( Object ) throws -> Void) throws
{
Expand All @@ -100,6 +102,9 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
yield: ( GlobalID ) throws -> Void) throws {
fatalError("implement in subclass: \(#function)")
}


// MARK: - Fetch Convenience

override open func fetchObjects(yield: ( Object ) -> Void) throws {
// `iteratorForObjects` in GETobjects
Expand All @@ -112,6 +117,58 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
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

Expand All @@ -130,6 +187,15 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {

// 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
Expand Down Expand Up @@ -165,7 +231,28 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
}
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
}
}
12 changes: 9 additions & 3 deletions Sources/ZeeQL/Access/AccessDataSourceError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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])
}

0 comments on commit 32ae98c

Please sign in to comment.