From ec72f3797120a88ba78a7ae6f52612cff3969728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Wed, 4 Dec 2024 16:14:10 +0100 Subject: [PATCH] The `TypedEntityType` actually works now ... modern Swift. --- .../Access/TypedFetchSpecification.swift | 2 + Sources/ZeeQL/Control/EntityType.swift | 111 +++++++++++++----- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/Sources/ZeeQL/Access/TypedFetchSpecification.swift b/Sources/ZeeQL/Access/TypedFetchSpecification.swift index 6d333e5..3d564db 100644 --- a/Sources/ZeeQL/Access/TypedFetchSpecification.swift +++ b/Sources/ZeeQL/Access/TypedFetchSpecification.swift @@ -25,6 +25,8 @@ public struct TypedFetchSpecification : DatabaseFetchSpecification { + // FIXME: Name, should be called "DynamicFetchSpecification" or something + public var _entity : Entity? = nil public var _entityName : String? diff --git a/Sources/ZeeQL/Control/EntityType.swift b/Sources/ZeeQL/Control/EntityType.swift index 599d1f8..63ed347 100644 --- a/Sources/ZeeQL/Control/EntityType.swift +++ b/Sources/ZeeQL/Control/EntityType.swift @@ -7,36 +7,51 @@ // /** - * This protocol is used for ORM classes which are guaranteed to have an Entity - * assigned - for example when the Entity is specified in code. + * This protocol is used for ORM classes which are guaranteed to have an + * ``Entity`` assigned, for example when the ``Entity`` is specified in code. * If a class has a fixed Entity, we can do more tricks in Swift. * * Example: - * - * class Address : ActiveRecord, EntityType { - * class Entity : OGoCodeEntity { - * let id = -1 - * let street : String? = nil - * let person = ToOne() - * } - * static let entity : ZeeQL.Entity = CodeEntity
(Entity()) - * } + * ```swift + * class Address : ActiveRecord, EntityType { + * class Entity : OGoCodeEntity { + * let id = -1 + * let street : String? = nil + * let person = ToOne() + * } + * static let entity : ZeeQL.Entity = CodeEntity
(Entity()) + * } + * ``` */ public protocol EntityType { // TODO: better name static var entity : Entity { get } } - -// TBD: This doesn't fly, I'm not completely sure why not. I cannot use it -// in the Object like `Person: ActiveRecord, GEntityType` due to the -// infamous: -// "Protocol … can only be used as a generic constraint because it has -// Self or associated type requirements" -public protocol GEntityType : EntityType { // TODO: better name +/** + * This protocol is used for ORM classes which are guaranteed to have an + * ``Entity`` assigned, for example when the ``Entity`` is specified in code. + * If a class has a fixed Entity, we can do more tricks in Swift. + * + * Example: + * ```swift + * class Address : ActiveRecord, TypedEntityType { + * class Entity : OGoCodeEntity { + * let id = -1 + * let street : String? = nil + * let person = ToOne() + * } + * static let e = CodeEntity
(Entity()) + * } + * ``` + */ +public protocol TypedEntityType : EntityType { // TODO: better name associatedtype FullEntity : ZeeQL.Entity static var e : FullEntity { get } } -public extension GEntityType { + +public extension TypedEntityType { + + @inlinable static var entity : ZeeQL.Entity { return e } } @@ -46,19 +61,7 @@ public extension GEntityType { public extension EntityType { // TBD: maybe rename, 'select' should run the actual select, right? - // TBD: what do we want? - // let objects = db.select(from: Persons.self) - // .where(Persons.login.like("*") - // .and(Persons.entity.addresses.zip.eq("39126")) - // .limit(4) - // .prefetch("addresses") - // if FetchSpec would be a generic, we could derive a lot from the type - // let fs = FetchSpecification - // .select(from: Person) -> GFetchSpecification - // .where(login.like ...) // login can access Person - // TBD: this could return a fetch-spec builder instead of recreating the - // specs all the time (FetchSpecificationRepresentable?) - + @inlinable static func select(_ attributes: String...) -> FetchSpecification { @@ -68,6 +71,7 @@ public extension EntityType { } // TBD: change this, 'select' should run the actual select, right? + @inlinable static func select(_ a1: Attribute, _ attributes: Attribute...) -> FetchSpecification { @@ -79,19 +83,62 @@ public extension EntityType { // MARK: - Qualifiers + @inlinable static func `where`(_ q: Qualifier) -> FetchSpecification { // if we need no attributes var fs = ModelFetchSpecification(entity: Self.entity) fs.qualifier = q return fs } + @inlinable static func `where`(_ q: String, _ args: Any?...) -> FetchSpecification { var fs = ModelFetchSpecification(entity: Self.entity) let parser = QualifierParser(string: q, arguments: args) fs.qualifier = parser.parseQualifier() return fs } +} +public extension TypedEntityType where Self: DatabaseObject { + // TBD: maybe rename, 'select' should run the actual select, right? + + @inlinable + static func select(_ attributes: String...) + -> TypedFetchSpecification + { + var fs = TypedFetchSpecification(entity: Self.entity) + fs.fetchAttributeNames = attributes.isEmpty ? nil : attributes + return fs + } + // TBD: change this, 'select' should run the actual select, right? + @inlinable + static func select(_ a1: Attribute, _ attributes: Attribute...) + -> TypedFetchSpecification + { + var fs = TypedFetchSpecification(entity: Self.entity) + fs.fetchAttributeNames = ([ a1 ] + attributes).map { $0.name } + return fs + } + + + // MARK: - Qualifiers + + @inlinable + static func `where`(_ q: Qualifier) -> TypedFetchSpecification { + // if we need no attributes + var fs = TypedFetchSpecification(entity: Self.entity) + fs.qualifier = q + return fs + } + @inlinable + static func `where`(_ q: String, _ args: Any?...) + -> TypedFetchSpecification + { + var fs = TypedFetchSpecification(entity: Self.entity) + let parser = QualifierParser(string: q, arguments: args) + fs.qualifier = parser.parseQualifier() + return fs + } }