diff --git a/Sources/ZeeQL/Access/Attribute.swift b/Sources/ZeeQL/Access/Attribute.swift index 9def57f..273e3f1 100644 --- a/Sources/ZeeQL/Access/Attribute.swift +++ b/Sources/ZeeQL/Access/Attribute.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Hess on 18/02/2017. -// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // /** @@ -148,7 +148,7 @@ extension Attribute { /** - * An Attribute description which stores the info as regular variables. + * An ``Attribute`` description which stores the info as regular variables. * * Suitable for use with models loaded from XML, or models fetched from a * database. @@ -175,8 +175,15 @@ open class ModelAttribute : Attribute, Equatable { public final var readFormat : String? public final var writeFormat : String? - // patterns - public final var isColumnNamePattern = false + /// Pattern types. + public enum PatternType: String, Sendable { + case none = "" + /// The columnName is a pattern + case columnName = "columnName" + /// The attribute should be skipped in the entity. + case skip = "skip" + } + public final var patternType : PatternType = .none public final var userData = [ String : Any ]() @@ -211,14 +218,14 @@ open class ModelAttribute : Attribute, Equatable { self.valueType = attr.valueType if let ma = attr as? ModelAttribute { - self.defaultValue = ma.defaultValue - self.comment = ma.comment - self.collation = ma.collation - self.privileges = ma.privileges - self.isColumnNamePattern = ma.isColumnNamePattern + self.defaultValue = ma.defaultValue + self.comment = ma.comment + self.collation = ma.collation + self.privileges = ma.privileges + self.patternType = ma.patternType - self.userData = ma.userData - self.elementID = ma.elementID + self.userData = ma.userData + self.elementID = ma.elementID } } @@ -233,13 +240,14 @@ open class ModelAttribute : Attribute, Equatable { // MARK: - Pattern Models public var isPattern : Bool { - if isColumnNamePattern { return true } - if externalType == nil { return true } + if patternType != .none { return true } + if externalType == nil { return true } + if allowsNull == nil { return true } return false } public func doesColumnNameMatchPattern(_ columnName: String) -> Bool { - if !isColumnNamePattern { return columnName == self.columnName } + if patternType != .columnName { return columnName == self.columnName } if self.columnName == "*" { return true } // match all // TODO: fix pattern handling, properly process '*' etc @@ -252,19 +260,41 @@ open class ModelAttribute : Attribute, Equatable { /* derive info */ let rAttr = ModelAttribute(attribute: self) - if let v = attr.externalType { rAttr.externalType = v } - if let v = attr.isAutoIncrement { rAttr.isAutoIncrement = v } - if let v = attr.allowsNull { rAttr.allowsNull = v } - if let v = attr.width { rAttr.width = v } - if let v = attr.readFormat { rAttr.readFormat = v } - if let v = attr.writeFormat { rAttr.writeFormat = v } - if let v = attr.defaultValue { rAttr.defaultValue = v } - if let v = attr.comment { rAttr.comment = v } - if let v = attr.collation { rAttr.collation = v } - if let v = attr.privileges { rAttr.privileges = v } + // The pattern *overrides* the external spec! If you want the information + // schema to win, leave out the value in the pattern. + if rAttr.externalType == nil, let v = attr.externalType { + rAttr.externalType = v + } + if rAttr.isAutoIncrement == nil, let v = attr.isAutoIncrement { + rAttr.isAutoIncrement = v + } + if rAttr.allowsNull == nil, let v = attr.allowsNull { + rAttr.allowsNull = v + } + if rAttr.width == nil, let v = attr.width { + rAttr.width = v + } + if rAttr.readFormat == nil, let v = attr.readFormat { + rAttr.readFormat = v + } + if rAttr.writeFormat == nil, let v = attr.writeFormat { + rAttr.writeFormat = v + } + if rAttr.defaultValue == nil, let v = attr.defaultValue { + rAttr.defaultValue = v + } + if rAttr.comment == nil, let v = attr.comment { + rAttr.comment = v + } + if rAttr.collation == nil, let v = attr.collation { + rAttr.collation = v + } + if rAttr.privileges == nil, let v = attr.privileges { + rAttr.privileges = v + } /* construct */ - rAttr.isColumnNamePattern = false // TBD: do we need to fix the colName? + rAttr.patternType = .none // TBD: do we need to fix the colName? return rAttr } @@ -272,7 +302,8 @@ open class ModelAttribute : Attribute, Equatable { attributes: [ Attribute ], entity: Entity? = nil) -> Bool { - if !isColumnNamePattern { + if patternType == .skip { return false } + if patternType != .columnName { /* check whether we are contained */ // TODO: is this correct, could be more than 1 attribute with the same // column? @@ -307,9 +338,9 @@ open class ModelAttribute : Attribute, Equatable { /* clone and add */ let attrCopy = ModelAttribute(attribute: self) - attrCopy.name = attr.name - attrCopy.columnName = attr.columnName - attrCopy.isColumnNamePattern = false + attrCopy.name = attr.name + attrCopy.columnName = attr.columnName + attrCopy.patternType = .none list.append(attrCopy) } return true @@ -343,10 +374,12 @@ open class ModelAttribute : Attribute, Equatable { if let cn = columnName { ms += "[" ms += cn - if isColumnNamePattern { ms += "*" } + if patternType == .columnName { ms += "*" } ms += "]" } + if patternType == .skip { ms += " SKIP" } + // TODO: precision let ws : String if let w = width { ws = "(\(w))" } diff --git a/Sources/ZeeQL/Access/AttributeKey.swift b/Sources/ZeeQL/Access/AttributeKey.swift index 8ef5ce0..541d0b7 100644 --- a/Sources/ZeeQL/Access/AttributeKey.swift +++ b/Sources/ZeeQL/Access/AttributeKey.swift @@ -3,9 +3,15 @@ // ZeeQL // // Created by Helge Hess on 02/03/17. -// Copyright © 2017 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +/** + * A ``Key`` that has access to an ``Attribute`` (an potentially the associated + * ``Entity``). + * + * The ``Key/key`` is the ``Attribute/name``. + */ public struct AttributeKey : Key, Equatable { public var key : String { return attribute.name } @@ -13,13 +19,14 @@ public struct AttributeKey : Key, Equatable { public let entity : Entity? public let attribute : Attribute + @inlinable public init(_ attribute: Attribute, entity: Entity? = nil) { self.attribute = attribute self.entity = entity } + @inlinable public static func ==(lhs: AttributeKey, rhs: AttributeKey) -> Bool { return lhs.key == rhs.key } - } diff --git a/Sources/ZeeQL/Access/CodeEntity.swift b/Sources/ZeeQL/Access/CodeEntity.swift index 3d4a910..9e39a7a 100644 --- a/Sources/ZeeQL/Access/CodeEntity.swift +++ b/Sources/ZeeQL/Access/CodeEntity.swift @@ -3,17 +3,26 @@ // ZeeQL // // Created by Helge Hess on 28/02/2017. -// Copyright © 2017 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +#if canImport(Foundation) +import Foundation +#endif + open class CodeEntityBase : Entity { // Those are available in subclasses, which makes it convenient // (can't do this in Generic classes, hence this intermediate) public enum Attribute { public typealias Int = CodeAttribute + public typealias OptInt = CodeAttribute public typealias String = CodeAttribute public typealias NullableString = CodeAttribute public typealias OptString = CodeAttribute + #if canImport(Foundation) + public typealias Date = CodeAttribute + public typealias OptDate = CodeAttribute + #endif } public typealias Info = Attribute diff --git a/Sources/ZeeQL/Access/CodeValueAttribute.swift b/Sources/ZeeQL/Access/CodeValueAttribute.swift index 06052c5..b67f6c2 100644 --- a/Sources/ZeeQL/Access/CodeValueAttribute.swift +++ b/Sources/ZeeQL/Access/CodeValueAttribute.swift @@ -3,9 +3,13 @@ // ZeeQL // // Created by Helge Hess on 06/03/17. -// Copyright © 2017 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +#if canImport(Foundation) +import Foundation +#endif + /** * CodeValueAttribute objects are used to describe properties of Entity objects * (which then become columns in the database) from within Swift source code @@ -96,7 +100,16 @@ public extension ActiveRecord { return box(name: name, column: column, externalType: externalType, value: value) } - + public static func OptInt(name : Swift.String? = nil, + column : Swift.String? = nil, + externalType : Swift.String? = nil, + _ value : Swift.Int? = nil) + -> CodeValueAttribute + { + return box(name: name, column: column, externalType: externalType, + value: value) + } + public static func String(name : Swift.String? = nil, column : Swift.String? = nil, externalType : Swift.String? = nil, @@ -120,6 +133,31 @@ public extension ActiveRecord { externalType: externalType, width: width, value: value) } + + #if canImport(Foundation) + public static func Date(name : Swift.String? = nil, + column : Swift.String? = nil, + externalType : Swift.String? = nil, + _ value : Foundation.Date = Foundation.Date()) + -> CodeValueAttribute + { + return box(name: name, column: column, + externalType: externalType, + value: value) + } + + public static func OptDate(name : Swift.String? = nil, + column : Swift.String? = nil, + externalType : Swift.String? = nil, + width : Swift.Int? = nil, + _ value : Foundation.Date? = nil) + -> CodeValueAttribute + { + return box(name: name, column: column, + externalType: externalType, width: width, + value: value) + } + #endif // canImport(Foundation) } } diff --git a/Sources/ZeeQL/Access/Entity.swift b/Sources/ZeeQL/Access/Entity.swift index ca06d94..61f1eae 100644 --- a/Sources/ZeeQL/Access/Entity.swift +++ b/Sources/ZeeQL/Access/Entity.swift @@ -226,12 +226,23 @@ public extension Entity { // default imp return AttributeKey(attr, entity: self) } + /** + * Iterates over the relationships and calls + * ``Relationship/connectRelationships(in:entity:)`` on them. + * + * - Parameters: + * - model: The model to resolve the entities. + */ @inlinable func connectRelationships(in model : Model) { for relship in relationships { relship.connectRelationships(in: model, entity: self) } } + /** + * Iterates over the relationships and calls + * ``Relationship/disconnectRelationships()`` on them. + */ @inlinable func disconnectRelationships() { for relship in relationships { @@ -239,6 +250,9 @@ public extension Entity { // default imp } } + /** + * Returns the names of the attributes and relationships of the entity. + */ @inlinable var classPropertyNames : [ String ]? { if attributes.isEmpty && relationships.isEmpty { return nil } diff --git a/Sources/ZeeQL/Access/ModelLoader.swift b/Sources/ZeeQL/Access/ModelLoader.swift index f625928..d4274f1 100644 --- a/Sources/ZeeQL/Access/ModelLoader.swift +++ b/Sources/ZeeQL/Access/ModelLoader.swift @@ -666,13 +666,21 @@ open class CoreDataModelLoader : ModelLoader { assert(xml.name == "attribute") let attrs = xml.attributesAsDict - let name = attrs["name"] ?? attrs["column"] ?? "" + let name = attrs["name"] ?? attrs["column"] ?? attrs["skip"] ?? "" // Note: we don't care about usesScalarValueType let attribute = ModelAttribute(name: name) - let allowsNull = boolValue(attrs["optional"] ?? attrs["null"]) - attribute.allowsNull = allowsNull - + if let v = attrs["optional"] ?? attrs["null"] { + attribute.allowsNull = boolValue(v) + } + else { + assert(attribute.allowsNull == nil) + } + + if name == attrs["skip"] { // + assert(attribute.patternType == .none) + attribute.patternType = .skip + } // GETobjects: if let v = attrs["column"], !v.isEmpty { attribute.columnName = v } if let v = attrs["width"].map({ Int($0) }) { attribute.width = v } @@ -680,9 +688,11 @@ open class CoreDataModelLoader : ModelLoader { if let v = attrs["columnNameLike"], !v.isEmpty { // assert(attribute.columnName == nil, "both columnName and columnNameLike?") + assert(attribute.patternType == .none) attribute.columnName = v - attribute.isColumnNamePattern = true + attribute.patternType = .columnName } + // CoreData if let v = attrs["elementID"], !v.isEmpty { attribute.elementID = v } @@ -690,41 +700,26 @@ open class CoreDataModelLoader : ModelLoader { if let attrType = attrs["attributeType"], !attrType.isEmpty { let lc = attrType.lowercased() + // Note: The valueType is NOT the Optional if the attribute is + // optional! if lc == "string" { - attribute.valueType = allowsNull ? Optional.self : String.self + attribute.valueType = String.self } else if lc.hasPrefix("int") { // funny, ?: doesn't work here - if attrType.hasSuffix("16") { - if allowsNull { attribute.valueType = Optional.self } - else { attribute.valueType = Int16.self } - } - else if attrType.hasSuffix("32") { - if allowsNull { attribute.valueType = Optional.self } - else { attribute.valueType = Int32.self } - } - else if attrType.hasSuffix("64") { - if allowsNull { attribute.valueType = Optional.self } - else { attribute.valueType = Int64.self } - } - else { - if allowsNull { attribute.valueType = Optional.self } - else { attribute.valueType = Int.self } - } - } - else if lc == "date" { - attribute.valueType = allowsNull ? Optional.self : Date.self + if attrType.hasSuffix("16") { attribute.valueType = Int16.self } + else if attrType.hasSuffix("32") { attribute.valueType = Int32.self } + else if attrType.hasSuffix("64") { attribute.valueType = Int64.self } + else { attribute.valueType = Int .self } } + else if lc == "date" { attribute.valueType = Date.self } else if lc == "binary" || lc == "data" { - attribute.valueType = allowsNull ? Optional.self : Data.self + attribute.valueType = Data.self } else if lc == "decimal" { // TODO: 'usesScalarValueType=YES' - if allowsNull { attribute.valueType = Optional.self } - else { attribute.valueType = Decimal.self } - } - else if lc.hasPrefix("bool") { - attribute.valueType = allowsNull ? Optional.self : Bool.self + attribute.valueType = Decimal.self } + else if lc.hasPrefix("bool") { attribute.valueType = Bool.self } else if lc.hasPrefix("transformable") { // valueTransformerName="xx" // customClassName="xx" @@ -819,8 +814,9 @@ open class CoreDataModelLoader : ModelLoader { if let v = attrs["elementID"], !v.isEmpty { attribute.elementID = v } - let allowsNull = boolValue(attrs["optional"]) - attribute.allowsNull = allowsNull + if let v = attrs["optional"] ?? attrs["null"] { + attribute.allowsNull = boolValue(v) + } relship.joins = [ Join(source : attributeName, destination : primaryKeyAttributeName) ] diff --git a/Sources/ZeeQL/Access/ModelPattern.swift b/Sources/ZeeQL/Access/ModelPattern.swift index 68651cf..302a67c 100644 --- a/Sources/ZeeQL/Access/ModelPattern.swift +++ b/Sources/ZeeQL/Access/ModelPattern.swift @@ -18,7 +18,8 @@ fileprivate extension ModelAttribute { entity: Entity, to attrs: inout [ Attribute ]) { - if !isColumnNamePattern { + if patternType == .skip { return } // do not add + if patternType != .columnName { /* check whether we are contained */ // TODO: is this correct, could be more than 1 attribute with the same // column? @@ -162,13 +163,6 @@ fileprivate extension ModelEntity { modelAttr.addAttributesMatchingAttributes(storedEntity.attributes, entity: self, to: &resolvedList) } - #if DEBUG - do { - var patCount = attributes.count - if attributes.last?.isPattern ?? false { patCount -= 1 } // * - assert(patCount <= resolvedList.count) - } - #endif /* fill column attributes */ @@ -220,13 +214,6 @@ fileprivate extension ModelEntity { } let lAttrs = resolvedList - #if DEBUG - do { - var patCount = attributes.count - if attributes.last?.isPattern ?? false { patCount -= 1 } // * - assert(patCount <= lAttrs.count) - } - #endif /* derive information from the peer */ @@ -237,7 +224,25 @@ fileprivate extension ModelEntity { let props : [ String ]? = nil // TODO: this would probably need some more work + #if false let rels = storedEntity.relationships + #else + let rels : [ Relationship ] + if relationships.isEmpty { + rels = storedEntity.relationships + } + else if storedEntity.relationships.isEmpty { + rels = relationships + } + else { + assertionFailure("Cannot (properly) merge relationships just yet.") + rels = storedEntity.relationships + relationships + } + // They are still connected to the original model, reset this. + rels.compactMap { $0 as? ModelRelationship }.forEach { + $0.destinationEntity = nil + } + #endif // not derived: // restrictingQualifier @@ -276,8 +281,20 @@ fileprivate extension ModelEntity { public extension Adaptor { + /** + * Resolves a pattern model against data fetched from the information schema. + * + * This does not touch the `model` property of the adaptor. It opens a channel + * to the database, fetches the information schema and then resolves the + * pattern against that. + * + * - Parameters: + * - pattern: A pattern Model, if the model is not a pattern, it is + * returned as is. + * - Returns: The resolved pattern model. + */ func resolveModelPattern(_ pattern: Model) throws -> Model? { - guard pattern.isPattern else { return pattern} + guard pattern.isPattern else { return pattern } var entities = pattern.entities if entities.isEmpty { /* not sure whether this is a good idea */ @@ -359,6 +376,16 @@ public extension Adaptor { log.info("finished resolving pattern model:", newModel) newModel.connectRelationships() + + #if DEBUG + for entity in entities { + for relship in entity.relationships { + assert(relship.isConnected) + assert(relship.destinationEntity != nil) + } + } + #endif + return newModel } } diff --git a/Sources/ZeeQL/Access/Relationship.swift b/Sources/ZeeQL/Access/Relationship.swift index 0dc5870..6f23436 100644 --- a/Sources/ZeeQL/Access/Relationship.swift +++ b/Sources/ZeeQL/Access/Relationship.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Hess on 18/02/2017. -// Copyright © 2017 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // @@ -229,8 +229,8 @@ public extension Relationship { // extra methods /** - * Checks whether the Relationship got resolved (whether the Entity of - * the destination entity was looked up). + * Checks whether the Relationship got resolved (whether the ``Entity`` of + * the destination entity was looked up and whether all joins are connected). */ var isConnected : Bool { if destinationEntity == nil { return false } @@ -305,7 +305,7 @@ public extension Relationship { ms += " \(name)" ms += isToMany ? "[1:n]" : "[1:1]" - ms += " \(entity.name)" + ms += " '\(entity.name)'" if let to = destinationEntity { ms += " to=\(to.name)" } @@ -526,7 +526,7 @@ open class ModelRelationship : Relationship { ms += " to=\(to.name)" } else if let ton = destinationEntityName { - ms += " to=\(ton)!" + ms += " to=\(ton)(unresolved)" } else { ms += " to=?" diff --git a/Sources/ZeeQL/Control/ComparisonOperation.swift b/Sources/ZeeQL/Control/ComparisonOperation.swift index e46a0ab..1a9bf1a 100644 --- a/Sources/ZeeQL/Control/ComparisonOperation.swift +++ b/Sources/ZeeQL/Control/ComparisonOperation.swift @@ -3,7 +3,7 @@ // ZeeQLTests // // Created by Helge Heß on 24.08.19. -// Copyright © 2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2019-2024 ZeeZide GmbH. All rights reserved. // // TODO: Update to modern Swift API standards (e.g. lowercase) @@ -105,6 +105,7 @@ public enum ComparisonOperation : Equatable, SmartDescription { ms += stringRepresentation } + @inlinable public static func ==(lhs: ComparisonOperation, rhs: ComparisonOperation) -> Bool { @@ -131,6 +132,7 @@ public extension ComparisonOperation { // Note: Had this as KeyValueQualifier, but this makes class-checks harder. // Not sure what the best Swift approach would be to avoid the Any + @inlinable func compare(_ a: Any?, _ b: Any?) -> Bool { // Everytime you compare an Any, a 🐄 dies. switch self { @@ -178,3 +180,7 @@ public extension ComparisonOperation { } } } + +#if swift(>=5.5) +extension ComparisonOperation: Sendable {} +#endif diff --git a/Sources/ZeeQL/Control/Key.swift b/Sources/ZeeQL/Control/Key.swift index 0ec6be9..8cced5d 100644 --- a/Sources/ZeeQL/Control/Key.swift +++ b/Sources/ZeeQL/Control/Key.swift @@ -3,9 +3,25 @@ // ZeeQL // // Created by Helge Hess on 15/02/2017. -// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +/** + * The `Key` protocol represents "keys" within qualifiers, like the left + * and right side of a ``KeyComparisonQualifier``. + * + * ``Attribute`` names, or directly ``Attribute``'s, or a ``KeyPath``. + * + * It has two main things: + * - ``key``: The key in the model, usually the name of an attribute, + * but a `.` separated keypath for ``KeyPath`` keys. + * - ``append``: A method to form KeyPath'es (like `person.home.street`). + * + * Implementors: + * - ``StringKey`` (just wraps the plain string) + * - ``AttributeKey`` (has direct reference to the ``Attribute``) + * - ``KeyPath`` + */ public protocol Key : Expression, ExpressionEvaluation, EquatableType, CustomStringConvertible { @@ -55,9 +71,11 @@ public extension Key { public struct StringKey : Key, Equatable { public let key : String - + + @inlinable public init(_ key: String) { self.key = key } + @inlinable public static func ==(lhs: StringKey, rhs: StringKey) -> Bool { return lhs.key == rhs.key } @@ -66,11 +84,17 @@ public struct StringKey : Key, Equatable { public struct KeyPath : Key, Equatable { public var keys : [ Key ] + + /// Combines the keys into a `.` separated KeyPath. + @inlinable public var key : String { return keys.map { $0.key }.joined(separator: ".") } + @inlinable public init(_ keys: Key...) { self.keys = keys } + @inlinable public init(keys: Key...) { self.keys = keys } + @inlinable public static func ==(lhs: KeyPath, rhs: KeyPath) -> Bool { // TODO: just compare the arrays guard lhs.keys.count == rhs.keys.count else { return false } @@ -88,16 +112,18 @@ public extension Key { extension StringKey : ExpressibleByStringLiteral { + @inlinable public init(stringLiteral value: String) { self.key = value } + @inlinable public init(extendedGraphemeClusterLiteral value: StringLiteralType) { self.key = value } + @inlinable public init(unicodeScalarLiteral value: StringLiteralType) { self.key = value } - } diff --git a/Sources/ZeeQL/Control/KeyComparisonQualifier.swift b/Sources/ZeeQL/Control/KeyComparisonQualifier.swift index 8aa744e..4359b9a 100644 --- a/Sources/ZeeQL/Control/KeyComparisonQualifier.swift +++ b/Sources/ZeeQL/Control/KeyComparisonQualifier.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Hess on 28/02/17. -// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // public struct KeyComparisonQualifier : Qualifier, Equatable { @@ -12,25 +12,33 @@ public struct KeyComparisonQualifier : Qualifier, Equatable { public let rightKeyExpr : Key public let operation : ComparisonOperation + @inlinable public init(_ left: Key, _ op: ComparisonOperation = .EqualTo, _ right: Key) { self.leftKeyExpr = left self.rightKeyExpr = right self.operation = op } + @inlinable public init(_ left: String, _ op: String = "==", _ right: String) { self.init(StringKey(left), ComparisonOperation(string: op), StringKey(right)) } + @inlinable public var leftKey : String { return leftKeyExpr.key } + @inlinable public var rightKey : String { return rightKeyExpr.key } + @inlinable public var leftExpression : Expression { return leftKeyExpr } + @inlinable public var rightExpression : Expression { return rightKeyExpr } + @inlinable public var isEmpty : Bool { return false } + @inlinable public func addReferencedKeys(to set: inout Set) { set.insert(leftKeyExpr.key) set.insert(rightKeyExpr.key) @@ -39,11 +47,13 @@ public struct KeyComparisonQualifier : Qualifier, Equatable { // MARK: - Bindings + @inlinable public var hasUnresolvedBindings : Bool { return false } // MARK: - Equality + @inlinable public static func ==(lhs: KeyComparisonQualifier, rhs: KeyComparisonQualifier) -> Bool { @@ -53,6 +63,7 @@ public struct KeyComparisonQualifier : Qualifier, Equatable { return true } + @inlinable public func isEqual(to object: Any?) -> Bool { guard let other = object as? KeyComparisonQualifier else { return false } return self == other diff --git a/Sources/ZeeQL/Foundation/EquatableType.swift b/Sources/ZeeQL/Foundation/EquatableType.swift index ea7db2d..ba5ff47 100644 --- a/Sources/ZeeQL/Foundation/EquatableType.swift +++ b/Sources/ZeeQL/Foundation/EquatableType.swift @@ -3,7 +3,7 @@ // ZeeQL // // Created by Helge Heß on 17.02.17. -// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved. +// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // /** @@ -34,6 +34,7 @@ public protocol LikeComparisonType { } +@inlinable public func eq(_ a: T?, _ b: T?) -> Bool { if let a = a, let b = b { return a == b @@ -42,6 +43,7 @@ public func eq(_ a: T?, _ b: T?) -> Bool { return a == nil && b == nil } } +@inlinable public func isSmaller(_ a: T?, _ b: T?) -> Bool { if let a = a, let b = b { return a < b @@ -54,18 +56,44 @@ public func isSmaller(_ a: T?, _ b: T?) -> Bool { } } +@inlinable public func eq(_ a: Any?, _ b: Any?) -> Bool { if let a = a, let b = b { - guard let a = a as? EquatableType else { return false } + guard let a = a as? EquatableType else { + #if swift(>=5.5) + // Unwrapping dance + func _isEqual(lhs: T, rhs: Any) -> Bool { + guard let rhs = rhs as? T else { return false } + return lhs == rhs + } + guard let lhs = a as? any Equatable else { return false } + return _isEqual(lhs: lhs, rhs: b) + #else + return false + #endif + } return a.isEqual(to: b) } else { return a == nil && b == nil } } +@inlinable public func isSmaller(_ a: Any?, _ b: Any?) -> Bool { if let a = a, let b = b { - guard let a = a as? ComparableType else { return false } + guard let a = a as? ComparableType else { + #if swift(>=5.5) + // Unwrapping dance + func _isSmaller(lhs: T, rhs: Any) -> Bool { + guard let rhs = rhs as? T else { return false } + return lhs < rhs + } + guard let lhs = a as? any Comparable else { return false } + return _isSmaller(lhs: lhs, rhs: b) + #else + return false + #endif + } return a.isSmaller(than: b) } else if a == nil && b == nil { @@ -82,6 +110,8 @@ public func isSmaller(_ a: Any?, _ b: Any?) -> Bool { // TBD: is there a betta way? :-) public extension FixedWidthInteger { + + @inlinable func isEqual(to object: Any?) -> Bool { guard let object = object else { return false } // other is nil switch object { @@ -99,6 +129,8 @@ public extension FixedWidthInteger { default: return false } } + + @inlinable func isSmaller(than object: Any?) -> Bool { guard let object = object else { return false } // other is nil switch object { @@ -128,20 +160,24 @@ extension UInt32 : EquatableType, ComparableType {} extension UInt64 : EquatableType, ComparableType {} extension Float : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Float else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? Float else { return false } return self < v } } extension Double : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Double else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? Double else { return false } return self < v @@ -149,6 +185,7 @@ extension Double : EquatableType, ComparableType { } extension String : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let object = object else { return false } // other is nil switch object { @@ -157,6 +194,7 @@ extension String : EquatableType, ComparableType { default: return false } } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let object = object else { return false } // other is nil switch object { @@ -168,10 +206,12 @@ extension String : EquatableType, ComparableType { } extension Bool : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Bool else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? Bool else { return false } return self == v ? false : !self @@ -186,10 +226,12 @@ import struct Foundation.Date import struct Foundation.DateComponents extension Date : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Date else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? Date else { return false } return self < v @@ -197,24 +239,28 @@ extension Date : EquatableType, ComparableType { } extension UUID : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? UUID else { return false } return self == v } } extension URL : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? URL else { return false } return self == v } } extension Data : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Data else { return false } return self == v } } extension DateComponents : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? DateComponents else { return false } return self == v @@ -224,10 +270,12 @@ extension DateComponents : EquatableType { import struct Foundation.DateInterval @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) extension DateInterval : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? DateInterval else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? DateInterval else { return false } return self < v @@ -237,10 +285,12 @@ extension DateInterval : EquatableType, ComparableType { import struct Foundation.Measurement @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) extension Measurement : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? Measurement else { return false } return self == v } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let v = object as? Measurement else { return false } return self < v @@ -250,6 +300,7 @@ extension Measurement : EquatableType, ComparableType { import struct Foundation.PersonNameComponents @available(OSX 10.11, iOS 9.0, *) extension PersonNameComponents : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { guard let v = object as? PersonNameComponents else { return false } return self == v @@ -257,6 +308,7 @@ extension PersonNameComponents : EquatableType { } extension Decimal : EquatableType, ComparableType { + @inlinable public func isEqual(to object: Any?) -> Bool { switch object { case let other as Decimal: return self == other @@ -273,6 +325,7 @@ extension Decimal : EquatableType, ComparableType { default: return false } } + @inlinable public func isSmaller(than object: Any?) -> Bool { guard let object = object else { return false } switch object { @@ -299,6 +352,7 @@ extension Optional where Wrapped : EquatableType { // this is not picked // For this: you’ll need conditional conformance. Swift 4, hopefully + @inlinable public func isEqual(to object: Any?) -> Bool { // TBD: do we want to compare optionals against non-optionals? In ObjC we // do, right? @@ -319,6 +373,7 @@ extension Optional where Wrapped : ComparableType { // this is not picked // For this: you’ll need conditional conformance. Swift 4, hopefully + @inlinable public func isSmaller(than object: Any?) -> Bool { // TBD: do we want to compare optionals against non-optionals? In ObjC we // do, right? @@ -340,6 +395,7 @@ extension Optional where Wrapped : ComparableType { extension Optional : EquatableType { + @inlinable public func isEqual(to object: Any?) -> Bool { // TBD: do we want to compare optionals against non-optionals? In ObjC we // do, right? @@ -365,6 +421,7 @@ extension Optional : EquatableType { } extension Optional : ComparableType { + @inlinable public func isSmaller(than object: Any?) -> Bool { // TBD: do we want to compare optionals against non-optionals? In ObjC we // do, right? @@ -394,6 +451,7 @@ extension Optional : ComparableType { // MARK: - Containment public extension Collection where Element : EquatableType { + @inlinable func contains(other object: Any?) -> Bool { return contains { $0.isEqual(to: object) } } @@ -410,6 +468,7 @@ extension Range : ContainsComparisonType where Element : EquatableType {} #if swift(>=5) public extension StringProtocol { + @inlinable func contains(other object: Any?) -> Bool { switch object { case .none: return false @@ -420,6 +479,7 @@ public extension StringProtocol { } } + @inlinable func isLike(other object: Any?, caseInsensitive ci: Bool) -> Bool { guard let other = object else { return false } // String not like nil if !ci { @@ -442,6 +502,7 @@ public extension StringProtocol { /// A simple (and incomplete) * pattern matcher. /// Can only do prefix/suffix/contains + @inlinable func likePatternMatch(_ pattern: P) -> Bool { guard pattern.contains("*") else { return self == pattern } let starPrefix = pattern.hasPrefix("*")