From ad557ac8268fb7224c757db319f4847a90f31567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Sat, 23 Nov 2024 14:02:24 +0100 Subject: [PATCH] Protocolize DataSource types This should eventually drop the classes and only use the protocols. Sensible in today's Swift. --- Sources/ZeeQL/Access/AccessDataSource.swift | 40 +++++++++-- Sources/ZeeQL/Access/ActiveDataSource.swift | 54 +++++++++----- Sources/ZeeQL/Access/AdaptorDataSource.swift | 61 ++++++++++------ Sources/ZeeQL/Access/DatabaseDataSource.swift | 14 +++- Sources/ZeeQL/Control/ArrayDataSource.swift | 4 +- Sources/ZeeQL/Control/DataSource.swift | 70 +++++++++++++------ 6 files changed, 175 insertions(+), 68 deletions(-) diff --git a/Sources/ZeeQL/Access/AccessDataSource.swift b/Sources/ZeeQL/Access/AccessDataSource.swift index b10c7a3..ea8b862 100644 --- a/Sources/ZeeQL/Access/AccessDataSource.swift +++ b/Sources/ZeeQL/Access/AccessDataSource.swift @@ -6,6 +6,30 @@ // Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +public protocol AccessDataSourceType: DataSourceType { + + var log : ZeeQLLogger { get } + + var entityName : String? { get set } + var entity : Entity? { get } + var fetchSpecificationName : String? { get set } + + var auxiliaryQualifier : Qualifier? { get set } + var isFetchEnabled : Bool { get set } + var qualifierBindings : Any? { get set } + + var qualifierBindingKeys : [ String ] { get } + + func fetchGlobalIDs(yield: ( GlobalID ) throws -> Void) throws + + // TODO: remove `_` + func _primaryFetchObjects (_ fs: FetchSpecification, + yield: ( Object ) throws -> Void) throws + func _primaryFetchCount (_ fs: FetchSpecification) throws -> Int + func _primaryFetchGlobalIDs(_ fs: FetchSpecification, + yield: ( GlobalID ) throws -> Void) throws +} + /** * This class has a set of operations targetted at SQL based applications. It * has three major subclasses with specific characteristics: @@ -35,7 +59,9 @@ * mapping, that is, it returns ``AdaptorRecord`` objects and works directly on * top of an ``AdaptorChannel``. */ -open class AccessDataSource : DataSource { +open class AccessDataSource : DataSource, + AccessDataSourceType +{ // TODO: Both DataSource and AccessDataSource should be protocols w/ PATs now, // generics are good enough in Swift now. @@ -102,7 +128,7 @@ open class AccessDataSource : DataSource { yield: ( GlobalID ) throws -> Void) throws { fatalError("implement in subclass: \(#function)") } - + // MARK: - Fetch Convenience @@ -110,13 +136,16 @@ open class AccessDataSource : DataSource { // `iteratorForObjects` in GETobjects try _primaryFetchObjects(try fetchSpecificationForFetch(), yield: yield) } - override open func fetchCount() throws -> Int { + open func fetchCount() throws -> Int { return try _primaryFetchCount(try fetchSpecificationForFetch()) } open func fetchGlobalIDs(yield: ( GlobalID ) throws -> Void) throws { try _primaryFetchGlobalIDs(try fetchSpecificationForFetch(), yield: yield) } +} +public extension AccessDataSourceType { + /** * 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 @@ -140,8 +169,8 @@ open class AccessDataSource : DataSource { * - keysAndValues: The key/value pairs to apply as bindings. * - Returns: The fetched objects. */ - public func fetchObjects(_ fetchSpecificationName: String, - _ keysAndValues: Any...) throws -> [ Object ] + func fetchObjects(_ fetchSpecificationName: String, + _ keysAndValues: Any...) throws -> [ Object ] { guard let findEntity = entity else { // TBD: improve exception @@ -172,6 +201,7 @@ open class AccessDataSource : DataSource { // MARK: - Bindings + @inlinable var qualifierBindingKeys : [ String ] { let q = fetchSpecification?.qualifier let aux = auxiliaryQualifier diff --git a/Sources/ZeeQL/Access/ActiveDataSource.swift b/Sources/ZeeQL/Access/ActiveDataSource.swift index c18bb34..c87235c 100644 --- a/Sources/ZeeQL/Access/ActiveDataSource.swift +++ b/Sources/ZeeQL/Access/ActiveDataSource.swift @@ -6,13 +6,39 @@ // Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +public protocol ActiveDataSourceType: AccessDataSourceType + where Object: ActiveRecordType +{ + + var database : Database { get set } + + init(database: Database, entity: Entity) + + func createObject() -> Object + func insertObject(_ object: Object) throws +} + +extension ActiveDataSourceType { + + @inlinable + public init(database: Database) { + if let t = Object.self as? EntityType.Type { + self.init(database: database, entity: t.entity) + } + else { + fatalError("Could not determine entity from object") + } + } + +} + /** * Used to query `DatabaseObject`s from a `Database`. * - * W/o it you usually create an - * Database object with an Adaptor and then use that database object to - * acquire an DatabaseChannel. - * + * W/o it you usually create a + * ``Database`` object with an ``Adaptor`` and then use that database object to + * acquire an ``DatabaseChannel``. + * * Naming convention: * - `find...` - a method which returns a single object and uses LIMIT 1 * - `fetch..` - a method which returns a List of objects @@ -20,16 +46,18 @@ * - `perform...` - a method which applies a change to the database * * Example: - * - * let persons = db.datasource(Person.self) + * ```swift + * let persons = db.datasource(Person.self) + * ``` */ -open class ActiveDataSource : AccessDataSource +open class ActiveDataSource: AccessDataSource, + ActiveDataSourceType { open var database : Database let _entity : Entity - public init(database: Database, entity: Entity) { + required public init(database: Database, entity: Entity) { self.database = database self._entity = entity @@ -38,16 +66,6 @@ open class ActiveDataSource : AccessDataSource self.log = database.log } - @inlinable - public convenience init(database: Database) { - if let t = Object.self as? EntityType.Type { - self.init(database: database, entity: t.entity) - } - else { - fatalError("Could not determine entity from object") - } - } - // MARK: - Create diff --git a/Sources/ZeeQL/Access/AdaptorDataSource.swift b/Sources/ZeeQL/Access/AdaptorDataSource.swift index 8ccd3cd..3af510b 100644 --- a/Sources/ZeeQL/Access/AdaptorDataSource.swift +++ b/Sources/ZeeQL/Access/AdaptorDataSource.swift @@ -6,6 +6,39 @@ // Copyright © 2017-2020 ZeeZide GmbH. All rights reserved. // +public protocol AdaptorDataSourceType: AccessDataSourceType + where Object == AdaptorRecord +{ + var adaptor : Adaptor { get } + + init(adaptor: Adaptor, entity: Entity?) +} + +public extension AdaptorDataSourceType { + + @inlinable + init(adaptor: Adaptor) { self.init(adaptor: adaptor, entity: nil) } + + @inlinable + func findEntity(for _entityName: String?) -> Entity? { + let ename : String + if let entityName = _entityName { + ename = entityName + } + else if let fs = fetchSpecification, let entityName = fs.entityName { + ename = entityName + } + else { + return nil + } + + /* retrieve model of adaptor */ + + guard let model = adaptor.model else { return nil } + return model[entity: ename] + } +} + /** * This datasource operates at the adaptor level, that is, it does not return * DatabaseObject instances but plain records. @@ -17,37 +50,21 @@ * * THREAD: this class is for use in one thread. */ -open class AdaptorDataSource : AccessDataSource { +open class AdaptorDataSource : AccessDataSource, + AdaptorDataSourceType +{ // TBD: do we need a way to initialize a datasource with an EOAdaptorChannel? - let adaptor : Adaptor + public let adaptor : Adaptor let _entity : Entity? - public init(adaptor: Adaptor, entity: Entity? = nil) { + required public init(adaptor: Adaptor, entity: Entity?) { self.adaptor = adaptor self._entity = entity } override open var entity : Entity? { - if let entity = _entity { return entity } // set explicitly - - /* determine name of datasource entity */ - - let ename : String - if let entityName = _entityName { - ename = entityName - } - else if let fs = fetchSpecification, let entityName = fs.entityName { - ename = entityName - } - else { - return nil - } - - /* retrieve model of adaptor */ - - guard let model = adaptor.model else { return nil } - return model[entity: ename] + return _entity ?? findEntity(for: _entityName) } override diff --git a/Sources/ZeeQL/Access/DatabaseDataSource.swift b/Sources/ZeeQL/Access/DatabaseDataSource.swift index a764151..7e8a9a3 100644 --- a/Sources/ZeeQL/Access/DatabaseDataSource.swift +++ b/Sources/ZeeQL/Access/DatabaseDataSource.swift @@ -6,17 +6,27 @@ // Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +public protocol DatabaseDataSourceType: AccessDataSourceType + where Object: DatabaseObject +{ + + var objectContext : ObjectTrackingContext { get } + var database : Database? { get } + + init(_ oc: ObjectTrackingContext, entityName: String) +} + /** * A datasource which works on top of an EditingContext. That is, the editing * context does all the fetching. */ open class DatabaseDataSource - : AccessDataSource + : AccessDataSource, DatabaseDataSourceType { public let objectContext : ObjectTrackingContext - public init(_ oc: ObjectTrackingContext, entityName: String) { + required public init(_ oc: ObjectTrackingContext, entityName: String) { self.objectContext = oc super.init() diff --git a/Sources/ZeeQL/Control/ArrayDataSource.swift b/Sources/ZeeQL/Control/ArrayDataSource.swift index e8318d5..70623e2 100644 --- a/Sources/ZeeQL/Control/ArrayDataSource.swift +++ b/Sources/ZeeQL/Control/ArrayDataSource.swift @@ -21,7 +21,7 @@ open class ArrayDataSource : DataSource, } @inlinable - override open func fetchCount() throws -> Int { + open func fetchCount() throws -> Int { return objects.count } @@ -94,6 +94,8 @@ open class ArrayDataSource : DataSource, } } } + + // MARK: - Description public func appendToDescription(_ ms: inout String) { ms += " #objects=\(objects.count)" diff --git a/Sources/ZeeQL/Control/DataSource.swift b/Sources/ZeeQL/Control/DataSource.swift index ac550a7..7cec58e 100644 --- a/Sources/ZeeQL/Control/DataSource.swift +++ b/Sources/ZeeQL/Control/DataSource.swift @@ -7,43 +7,74 @@ // /** - * A DataSource performs a query against some 'entity', in the ORM usually a - * database table (which is mapped to an Entity). + * A DataSource performs a query against something, in the ORM usually a + * database table (which is mapped to an ``Entity``). * * The ZeeQL DataSources always have an FetchSpecification which specifies * the environment for fetches. * * The DataSource is very general, but ORM specific subclasses include: - * - DatabaseDataSource - * - ActiveDataSource - * - AdaptorDataSource + * - ``DatabaseDataSource`` + * - ``ActiveDataSource`` + * - ``AdaptorDataSource`` */ -open class DataSource: EquatableType, Equatable { - // Used to be a protocol, but Swift 3 and generic protocols .... +public protocol DataSourceType: EquatableType, Equatable { - open var fetchSpecification : FetchSpecification? + associatedtype Object: SwiftObject - open func fetchObjects(yield: ( Object ) -> Void) throws { - fatalError("Subclass must implement: \(#function)") - } - open func fetchCount() throws -> Int { // inefficient default implementation + var fetchSpecification : FetchSpecification? { get set } + + func fetchObjects(yield: ( Object ) -> Void) throws + func fetchCount() throws -> Int +} + +public extension DataSourceType { + + @inlinable + func fetchCount() throws -> Int { // inefficient default implementation var count = 0 try fetchObjects(yield: { _ in count += 1 }) return count } - - // MARK: - Equatable +} + +public extension DataSourceType where Self: AnyObject { // Equatable @inlinable - public func isEqual(to object: Any?) -> Bool { - guard let other = object as? DataSource else { return false } + func isEqual(to object: Any?) -> Bool { + guard let other = object as? Self else { return false } return other.isEqual(to: self) } @inlinable - public func isEqual(to object: DataSource) -> Bool { - return self === object + func isEqual(to object: Self) -> Bool { return self === object } + + @inlinable + static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.isEqual(to: rhs) } +} + +/** + * A DataSource performs a query against something, in the ORM usually a + * database table (which is mapped to an ``Entity``). + * + * The ZeeQL DataSources always have an FetchSpecification which specifies + * the environment for fetches. + * + * The DataSource is very general, but ORM specific subclasses include: + * - ``DatabaseDataSource`` + * - ``ActiveDataSource`` + * - ``AdaptorDataSource`` + */ +open class DataSource: DataSourceType { + // Used to be a protocol, but Swift 3 and generic protocols .... + + open var fetchSpecification : FetchSpecification? + + open func fetchObjects(yield: ( Object ) -> Void) throws { + fatalError("Subclass must implement: \(#function)") } + // MARK: - Equatable + @inlinable public static func ==(lhs: DataSource, rhs: DataSource) -> Bool { return lhs.isEqual(to: rhs) @@ -59,7 +90,7 @@ public protocol SwiftObject: AnyObject { // is there a standard protocol for this? `AnyObject` also does @objc ... } -public extension DataSource { +public extension DataSourceType { @inlinable func fetchObjects() throws -> [ Object ] { @@ -67,5 +98,4 @@ public extension DataSource { try fetchObjects { objects.append($0) } return objects } - }