diff --git a/Sources/ZeeQL/Access/ActiveRecordType.swift b/Sources/ZeeQL/Access/ActiveRecordType.swift index 35cc036..d9d2a30 100644 --- a/Sources/ZeeQL/Access/ActiveRecordType.swift +++ b/Sources/ZeeQL/Access/ActiveRecordType.swift @@ -34,9 +34,9 @@ public protocol ActiveRecordType : DatabaseObject, } public extension ActiveRecordType { - var globalID: GlobalID? { - return entity.globalIDForRow(self) - } + + @inlinable + var globalID: GlobalID? { return entity.globalIDForRow(self) } } public protocol DatabaseBoundObject { diff --git a/Sources/ZeeQL/Access/AdaptorChannel.swift b/Sources/ZeeQL/Access/AdaptorChannel.swift index e30e823..3edd63f 100644 --- a/Sources/ZeeQL/Access/AdaptorChannel.swift +++ b/Sources/ZeeQL/Access/AdaptorChannel.swift @@ -29,7 +29,7 @@ public protocol AdaptorChannel : AdaptorQueryType, ModelNameMapper { func begin() throws func commit() throws func rollback() throws - var isTransactionInProgress : Bool { get } + var isTransactionInProgress : Bool { get } // MARK: - Reflection @@ -45,8 +45,8 @@ public protocol AdaptorChannel : AdaptorQueryType, ModelNameMapper { // MARK: - Adaptor Operations - func performAdaptorOperations(_ ops : [ AdaptorOperation ]) throws - func performAdaptorOperation (_ op : AdaptorOperation) throws + func performAdaptorOperations(_ ops : inout [ AdaptorOperation ]) throws + func performAdaptorOperation (_ op : inout AdaptorOperation) throws // MARK: - Operations @@ -184,7 +184,7 @@ public extension AdaptorChannel { * - parameters: * - ops: the array of AdaptorOperation's to be performed */ - func performAdaptorOperations(_ ops: [ AdaptorOperation ]) throws { + func performAdaptorOperations(_ ops: inout [ AdaptorOperation ]) throws { // TBD: we should probably open a transaction if count > 1? Or is this the // responsibility of the user? @@ -202,8 +202,8 @@ public extension AdaptorChannel { if didOpenTx { try begin() } do { - for op in ops { - try performAdaptorOperation(op) + for idx in ops.indices { + try performAdaptorOperation(&ops[idx]) } } catch { @@ -215,15 +215,15 @@ public extension AdaptorChannel { } /** - * This calls performAdaptorOperationN() and returns a success (null) when - * exactly one row was affected. + * This calls ``performAdaptorOperationN(_:)`` and returns a success (null) + * when exactly one row was affected. * - * - parameters: - * - op: the `AdaptorOperation` object + * - Parameters: + * - op: the ``AdaptorOperation`` to be performed */ @inlinable - func performAdaptorOperation(_ op: AdaptorOperation) throws { - let affectedRows = try performAdaptorOperationN(op) + func performAdaptorOperation(_ op: inout AdaptorOperation) throws { + let affectedRows = try performAdaptorOperationN(&op) guard affectedRows == 1 else { throw AdaptorChannelError.OperationDidNotAffectOne } @@ -234,11 +234,14 @@ public extension AdaptorChannel { * or deleteRowsDescri...() with the information contained in the operation * object. * - * This method is different to performAdaptorOperation() [w/o 'N' ;-)] + * This method is different to ``performAdaptorOperation(_:)-5j8fn`` [w/o 'N'] * because it returns the count of affected objects (eg how many rows got * deleted or updated). + * + * - Parameters: + * - op: the ``AdaptorOperation`` to be performed */ - func performAdaptorOperationN(_ op: AdaptorOperation) throws -> Int { + func performAdaptorOperationN(_ op: inout AdaptorOperation) throws -> Int { // TBD: we might want to move evaluation to this method and make // updateValuesInRows..() etc create AdaptorOperation's. This might // easen the creation of non-SQL adaptors. @@ -291,14 +294,26 @@ public extension AdaptorChannel { return affectedRows } + // MARK: - Adaptor Operations + + @inlinable + func performAdaptorOperations(_ ops : [ AdaptorOperation ]) throws { + var ops = ops + try performAdaptorOperations(&ops) + } + @inlinable + func performAdaptorOperation(_ op : AdaptorOperation) throws { + var op = op + try performAdaptorOperation(&op) + } } /** * A `[ String : Any? ]` dictionary with an optional value to represent * NULL columns. * - * So not mix up w/ `AdaptorRecord`, which are primarily used for fetching - * (to share the keys of a schema) + * Not to be mixed up w/ ``AdaptorRecord``, which are primarily used for + * fetching (to share the keys of a schema). */ public typealias AdaptorRow = Dictionary @@ -317,8 +332,7 @@ public extension AdaptorChannel { // MARK: - Operations func lockRowComparingAttributes(_ attrs : [ Attribute ]?, _ entity : Entity, _ qualifier : Qualifier?, - _ snapshot : AdaptorRow?) throws - -> Bool + _ snapshot : AdaptorRow?) throws -> Bool { let q = snapshot != nil ? qualifierToMatchAllValues(snapshot!) : nil let fs = ModelFetchSpecification(entity: entity, diff --git a/Sources/ZeeQL/Access/AdaptorOperation.swift b/Sources/ZeeQL/Access/AdaptorOperation.swift index bdfb591..060dc19 100644 --- a/Sources/ZeeQL/Access/AdaptorOperation.swift +++ b/Sources/ZeeQL/Access/AdaptorOperation.swift @@ -11,9 +11,36 @@ * an INSERT. The object keeps all the relevant information to calculate the * SQL for the operation. */ -public class AdaptorOperation: Comparable, EquatableType, SmartDescription { +public struct AdaptorOperation: Comparable, EquatableType, SmartDescription { // Note: an object because we also return values using this + @inlinable + public static func update(_ e: Entity, set: AdaptorRow, where q: Qualifier) + -> Self + { + var me = AdaptorOperation(entity: e) + me.adaptorOperator = .update + me.changedValues = set + me.qualifier = q + return me + } + + @inlinable + public static func insert(_ values: AdaptorRow, into e: Entity) -> Self { + var me = AdaptorOperation(entity: e) + me.adaptorOperator = .insert + me.changedValues = values + return me + } + + @inlinable + public static func delete(from e: Entity, where q: Qualifier) -> Self { + var me = AdaptorOperation(entity: e) + me.adaptorOperator = .delete + me.qualifier = q + return me + } + public enum Operator : Int { /* Note: sequence is relevant for comparison */ case none = 0 case lock = 1 @@ -34,7 +61,7 @@ public class AdaptorOperation: Comparable, EquatableType, SmartDescription { public var resultRow : AdaptorRow? /// Run when the operation did complete - open var completionBlock : (() -> ())? + public var completionBlock : (() -> ())? @inlinable public init(entity: Entity) { @@ -95,7 +122,7 @@ public class AdaptorOperation: Comparable, EquatableType, SmartDescription { if bq == q { return self } // no change - let ao = AdaptorOperation(self) // copy + var ao = AdaptorOperation(self) // copy ao.qualifier = bq return ao } @@ -123,5 +150,6 @@ public class AdaptorOperation: Comparable, EquatableType, SmartDescription { } #if swift(>=5.5) +// AdaptorOperation itself can't be sent yet due to the closure. extension AdaptorOperation.Operator: Sendable {} #endif diff --git a/Sources/ZeeQL/Access/Attribute.swift b/Sources/ZeeQL/Access/Attribute.swift index 768712b..803edc2 100644 --- a/Sources/ZeeQL/Access/Attribute.swift +++ b/Sources/ZeeQL/Access/Attribute.swift @@ -6,6 +6,8 @@ // Copyright © 2017-2024 ZeeZide GmbH. All rights reserved. // +import Foundation + /** * Usually represents a column in a database table. * @@ -17,59 +19,97 @@ * name3 columns. * * Model example: - * - * + * ```xml + * + * ``` * * ## Write Formats * * 'Write formats' are very useful to lower- or uppercase a value which is - * you want to search case-insensitive later on. Eg: + * you want to search case-insensitive later on. for example: + * ```xml + * writeformat="LOWER(TRIM(%P))" + * ``` * - * writeformat="LOWER(TRIM(%P))" - * - * This should be done at write time because if you use LOWER in a WHERE + * This should be done at write time because if you use `LOWER` in a `WHERE` * condition the database might not be able to use the index! - * (well, at least in PostgreSQL you can put an index on the LOWER + * (well, at least in PostgreSQL you can put an index on the `LOWER` * transformation, so it _can_ use an index) */ public protocol Attribute : Property, SQLValue, ExpressionEvaluation, SmartDescription -{ - var name : String { get } - var columnName : String? { get } - var externalType : String? { get } - var allowsNull : Bool? { get } - var isAutoIncrement : Bool? { get } - var width : Int? { get } - var precision : Int? { get } +{ + // TBD: `ModelAttribute` and its subclasses are the sole implementers, right? + // This one is "readonly" though. + + var name : String { get } + var columnName : String? { get } + var externalType : String? { get } + var allowsNull : Bool? { get } + var isAutoIncrement : Bool? { get } + var width : Int? { get } + var precision : Int? { get } var valueType : AttributeValue.Type? { get } + var defaultValue : Any? { get } + + var patternType : AttributePatternType { get } + // MySQL (PG 8.2 has comments on column, but no column privileges?) + var comment : String? { get } + var collation : String? { get } + var privileges : [ String ]? { get } + // formatting (used by SQLExpression) - var readFormat : String? { get } - var writeFormat : String? { get } + var readFormat : String? { get } + var writeFormat : String? { get } - var isPattern : Bool { get } + var isPattern : Bool { get } + + var userData : [ String : Any ] { get } + + /// A persistent ID used to track renaming when doing model-to-model + /// migrations. Used in Core Data. + var elementID : String? { get } + + /// CoreData (e.g. for `Date` attributes) + var usesScalarValueType : Bool? { get } + /// CoreData: Minimum timestamp for Date, if set + var minDateTimeInterval : Date? { get } + /// CoreData: Maximum timestamp for Date, if set + var maxDateTimeInterval : Date? { get } + /// CoreData: If it is a derived attribute this contains the expression. + var derivationExpression : String? { get } } public extension Attribute { // default imp // Note: dupe those in classes to avoid surprises! - var columnName : String? { return nil } - var externalType : String? { return nil } - var allowsNull : Bool? { return nil } - var isAutoIncrement : Bool? { return nil } - var width : Int? { return nil } - var precision : Int? { return nil } - - var valueType : AttributeValue.Type? { return nil } - - // formatting (used by SQLExpression) - var readFormat : String? { return nil } - var writeFormat : String? { return nil } + var columnName : String? { return nil } + var externalType : String? { return nil } + var allowsNull : Bool? { return nil } + var isAutoIncrement : Bool? { return nil } + var width : Int? { return nil } + var precision : Int? { return nil } - var isPattern : Bool { return false } + var patternType : AttributePatternType { return .none } + var valueType : AttributeValue.Type? { return nil } + var defaultValue : Any? { return nil } + + var comment : String? { return nil } + var collation : String? { return nil } + var privileges : [ String ]? { return nil } + + var readFormat : String? { return nil } + var writeFormat : String? { return nil } + + var isPattern : Bool { return false } + var usesScalarValueType : Bool? { return nil } + var minDateTimeInterval : Date? { return nil } + var maxDateTimeInterval : Date? { return nil } + var derivationExpression : String? { return nil } + // MARK: - Property var relationshipPath : String? { // for flattened properties @@ -78,12 +118,14 @@ public extension Attribute { // default imp // MARK: - SQLValue + @inlinable func valueFor(SQLExpression context: SQLExpression) -> String { return context.sqlStringFor(schemaObjectName: columnName ?? name) } // MARK: - ExpressionEvaluation + @inlinable func valueFor(object: Any?) -> Any? { return KeyValueCoding.value(forKeyPath: name, inObject: object) } @@ -115,6 +157,7 @@ public extension Attribute { // default imp public extension Attribute { // default imp + @inlinable func isEqual(to object: Any?) -> Bool { guard let other = object as? Attribute else { return false } return other.isEqual(to: self) @@ -133,6 +176,19 @@ public extension Attribute { // default imp guard readFormat == other.readFormat else { return false } guard writeFormat == other.writeFormat else { return false } guard isPattern == other.isPattern else { return false } + guard patternType == other.patternType else { return false } + + guard elementID == other.elementID else { return false } + guard usesScalarValueType == other.usesScalarValueType else { return false } + guard minDateTimeInterval == other.minDateTimeInterval else { return false } + guard maxDateTimeInterval == other.maxDateTimeInterval else { return false } + guard derivationExpression == other.derivationExpression else { + return false + } + + guard ZeeQL.eq(defaultValue, other.defaultValue) else { return false } + + // TBD: userData return true } @@ -146,6 +202,15 @@ extension Attribute { var columnNameOrName : String { return columnName ?? name } } +/// Pattern types. +public enum AttributePatternType: String, Sendable { + case none = "" + /// The columnName is a pattern + case columnName = "columnName" + /// The attribute should be skipped in the entity. + case skip = "skip" +} + /** * An ``Attribute`` description which stores the info as regular variables. @@ -154,6 +219,8 @@ extension Attribute { * database. */ open class ModelAttribute : Attribute, Equatable { + // TBD: Would be good to make this a struct, but it is currently subclassed + // for the Code based models. Maybe they could just wrap it? public final var name : String public final var columnName : String? @@ -175,22 +242,23 @@ open class ModelAttribute : Attribute, Equatable { public final var readFormat : String? public final var writeFormat : String? - /// 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 patternType = AttributePatternType.none public final var userData = [ String : Any ]() /// A persistent ID used to track renaming when doing model-to-model - /// migrations. + /// migrations. Used in Core Data. public final var elementID : String? + /// CoreData (e.g. for `Date` attributes) + public final var usesScalarValueType : Bool? + /// CoreData: Minimum timestamp for Date, if set + public final var minDateTimeInterval : Date? + /// CoreData: Maximum timestamp for Date, if set + public final var maxDateTimeInterval : Date? + /// CoreData: If it is a derived attribute this contains the expression. + public final var derivationExpression : String? + public init(name : String, column : String? = nil, externalType : String? = nil, @@ -217,16 +285,18 @@ open class ModelAttribute : Attribute, Equatable { self.writeFormat = attr.writeFormat 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.patternType = ma.patternType - - self.userData = ma.userData - self.elementID = ma.elementID - } + self.defaultValue = attr.defaultValue + self.comment = attr.comment + self.collation = attr.collation + self.privileges = attr.privileges + self.patternType = attr.patternType + + self.userData = attr.userData + self.elementID = attr.elementID + self.usesScalarValueType = attr.usesScalarValueType + self.minDateTimeInterval = attr.minDateTimeInterval + self.maxDateTimeInterval = attr.maxDateTimeInterval + self.derivationExpression = attr.derivationExpression } diff --git a/Sources/ZeeQL/Access/DatabaseChannel.swift b/Sources/ZeeQL/Access/DatabaseChannel.swift index fa3dce4..09df080 100644 --- a/Sources/ZeeQL/Access/DatabaseChannel.swift +++ b/Sources/ZeeQL/Access/DatabaseChannel.swift @@ -861,14 +861,19 @@ open class DatabaseChannelBase { /* turn db ops into adaptor ops */ - let aops = try adaptorOperationsForDatabaseOperations(ops) + var aops = try adaptorOperationsForDatabaseOperations(ops) guard !aops.isEmpty else { return } /* nothing to do */ /* perform adaptor ops */ var didOpenChannel = false - if adaptorChannel == nil { + let adaptorChannel : AdaptorChannel + if let c = self.adaptorChannel { + adaptorChannel = c + } + else { adaptorChannel = try acquireChannel() + self.adaptorChannel = adaptorChannel didOpenChannel = true } defer { @@ -879,7 +884,7 @@ open class DatabaseChannelBase { // Transactions: The `AdaptorChannel` opens a transaction if there is more // than one operation. Also: the `Database` embeds it into a TX too. - try adaptorChannel!.performAdaptorOperations(aops) + try adaptorChannel.performAdaptorOperations(&aops) // OK, the database operations have been successful. Now we need to handle @@ -971,7 +976,7 @@ open class DatabaseChannelBase { for op in ops { let entity = op.entity - let aop = AdaptorOperation(entity: entity) + var aop = AdaptorOperation(entity: entity) var dbop = op.databaseOperator if case .none = op.databaseOperator { diff --git a/Sources/ZeeQL/Access/Entity.swift b/Sources/ZeeQL/Access/Entity.swift index 6da00a1..1117371 100644 --- a/Sources/ZeeQL/Access/Entity.swift +++ b/Sources/ZeeQL/Access/Entity.swift @@ -36,7 +36,8 @@ public protocol Entity: AnyObject, EquatableType, SmartDescription { var attributes : [ Attribute ] { get } var relationships : [ Relationship ] { get } var primaryKeyAttributeNames : [ String ]? { get } - + // TBD: ^^^ should those be non-optional? (hh 2024-12-21) + /** * Returns the names of class property attributes and relationships. Those are * attributes which are exposed as a part of the database object. diff --git a/Sources/ZeeQL/Access/ModelLoader.swift b/Sources/ZeeQL/Access/ModelLoader.swift index 30dbfc9..6a96c70 100644 --- a/Sources/ZeeQL/Access/ModelLoader.swift +++ b/Sources/ZeeQL/Access/ModelLoader.swift @@ -698,6 +698,7 @@ open class CoreDataModelLoader : ModelLoader { attribute.valueType = String.self } else if lc.hasPrefix("int") { // funny, ?: doesn't work here + // Example: attributeType="Integer 64" 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 } @@ -722,6 +723,43 @@ open class CoreDataModelLoader : ModelLoader { } } + + if let v = attrs["usesScalarValueType"] { + attribute.usesScalarValueType = boolValue(v) + } + if let v = attrs["defaultValueString"] { + assert(attribute.defaultValue == nil) + attribute.defaultValue = v + } + if let v = attrs["defaultDateTimeInterval"], let i = Double(v) + { + assert(attribute.defaultValue == nil) + attribute.defaultValue = Date(timeIntervalSinceReferenceDate: i) + } + if let v = attrs["minDateTimeInterval"], let i = Double(v) { + attribute.minDateTimeInterval = Date(timeIntervalSinceReferenceDate: i) + } + if let v = attrs["maxDateTimeInterval"], let i = Double(v) { + attribute.maxDateTimeInterval = Date(timeIntervalSinceReferenceDate: i) + } + + if boolValue(attrs["derived"]) { + attribute.derivationExpression = attrs["derivationExpression"] + assert(attribute.derivationExpression != nil) + } + else { + attribute.derivationExpression = nil + } + + if attribute.defaultValue == nil, let v = attrs["default"] { + if attribute.valueType == Date.self, let i = Double(v) { + attribute.defaultValue = Date(timeIntervalSinceReferenceDate: i) + } + else { + attribute.defaultValue = v + } + } + return attribute } diff --git a/Sources/ZeeQL/Control/ComparisonOperation.swift b/Sources/ZeeQL/Control/ComparisonOperation.swift index f87de8e..5c8d347 100644 --- a/Sources/ZeeQL/Control/ComparisonOperation.swift +++ b/Sources/ZeeQL/Control/ComparisonOperation.swift @@ -15,7 +15,10 @@ public enum ComparisonOperation: Equatable { case Unknown(String) case EqualTo, NotEqualTo, GreaterThan, GreaterThanOrEqual - case LessThan, LessThanOrEqual, Contains + case LessThan, LessThanOrEqual + + // An `IN` query, e.g. `id IN %@`, where the %@ resolves to a collection. + case Contains /** * Compare the left hand side against a pattern. The `*` is used as the diff --git a/Sources/ZeeQL/Control/FetchSpecification+Builder.swift b/Sources/ZeeQL/Control/FetchSpecification+Builder.swift index de21b90..aa5dbdc 100644 --- a/Sources/ZeeQL/Control/FetchSpecification+Builder.swift +++ b/Sources/ZeeQL/Control/FetchSpecification+Builder.swift @@ -83,6 +83,11 @@ public extension FetchSpecification { @inlinable func offset(_ value : Int) -> Self { transform { $0.fetchOffset = value } } + @inlinable + func distinct(_ usesDistinct: Bool = true) -> Self { + transform { $0.usesDistinct = usesDistinct } + } + // MARK: - Prefetches @@ -283,5 +288,19 @@ public extension DatabaseFetchSpecification } } + @inlinable + func select( + _ attribute: repeat Swift.KeyPath, + clear: Bool = false + ) -> Self + { + transform { + if clear { $0.fetchAttributeNames = [] } + for attributePath in repeat each attribute { + let attribute = Object.e[keyPath: attributePath] + $0.fetchAttributeNames.append(attribute.name) + } + } + } #endif // compiler(>=6) } diff --git a/Sources/ZeeQL/Foundation/SimpleKVC.swift b/Sources/ZeeQL/Foundation/SimpleKVC.swift index 5bce845..a1685f1 100644 --- a/Sources/ZeeQL/Foundation/SimpleKVC.swift +++ b/Sources/ZeeQL/Foundation/SimpleKVC.swift @@ -3,7 +3,7 @@ // ZeeQL3 // // Created by Helge Heß on 6/1/16. -// Copyright © 2016-2021 ZeeZide GmbH. All rights reserved. +// Copyright © 2016-2024 ZeeZide GmbH. All rights reserved. // import class Foundation.NSObject @@ -19,8 +19,19 @@ public protocol MutableKeyValueCodingType : AnyObject { // Well, it could return 'self' with the updated struct? func takeValue(_ value : Any?, forKey k: String) throws + func takeValues(_ values : [ String : Any? ]) throws +} + +public extension MutableKeyValueCodingType { + @inlinable + func takeValues(_ values : [ String : Any? ]) throws { + for ( key, value ) in values { + try takeValue(value, forKey: key) + } + } } + public protocol KeyValueCodingTargetValue : AnyObject { // Again, only makes sense for classes? But we'd like to have structs. func setValue(_ value: Any?) throws @@ -28,6 +39,7 @@ public protocol KeyValueCodingTargetValue : AnyObject { public extension KeyValueCodingType { + @inlinable func value(forKey k: String) -> Any? { return KeyValueCoding.defaultValue(forKey: k, inObject: self) } @@ -37,6 +49,7 @@ public extension KeyValueCodingType { public extension KeyValueCodingType { // TODO: own protocol for that + @inlinable func values(forKeys keys: [String]) -> [ String : Any ] { var values = [ String : Any ]() values.reserveCapacity(keys.count) @@ -59,12 +72,14 @@ public struct KeyValueCoding { case CannotTakeValueForKey(String) } + @inlinable public static func takeValue(_ v: Any?, forKeyPath p: String, inObject o: Any?) throws { let path = p.split(separator: ".").map(String.init) try takeValue(v, forKeyPath: path, inObject: o) } + @inlinable public static func takeValue(_ v: Any?, forKeyPath p: [ String ], inObject o: Any?) throws { @@ -79,11 +94,13 @@ public struct KeyValueCoding { try takeValue(v, forKey: p[p.count - 1], inObject: t) } + @inlinable public static func value(forKeyPath p: String, inObject o: Any?) -> Any? { let path = p.split(separator: ".").map(String.init) return value(forKeyPath: path, inObject: o) } + @inlinable public static func value(forKeyPath p: [ String ], inObject o: Any?) -> Any? { var cursor = o for key in p { @@ -93,6 +110,7 @@ public struct KeyValueCoding { return cursor } + @inlinable public static func takeValue(_ v: Any?, forKey k: String, inObject o: Any?) throws { @@ -114,6 +132,7 @@ public struct KeyValueCoding { } } + @inlinable public static func value(forKey k: String, inObject o: Any?) -> Any? { if let kvc = o as? KeyValueCodingType { return kvc.value(forKey: k) @@ -121,6 +140,7 @@ public struct KeyValueCoding { return defaultValue(forKey: k, inObject: o) } + @inlinable public static func defaultValue(forKey k: String, inObject o: Any?) -> Any? { // Presumably this is really inefficient, but well :-) guard let object = o else { return nil } @@ -158,6 +178,7 @@ public struct KeyValueCoding { return nil } + @inlinable public static func values(forKeys keys: [String], inObject o: Any?) -> [ String : Any ] { @@ -179,6 +200,7 @@ public struct KeyValueCoding { public extension KeyValueCoding { + @inlinable static func defaultValue(forKey k: String, inDictionary o: Any, mirror: Mirror) -> Any? { @@ -223,6 +245,7 @@ extension Dictionary: KeyValueCodingType /*, MutableKeyValueCodingType */ { // MutableKeyValueCodingType only really makes sense for classes, right? // Well, it could return 'self' with the updated struct? + @inlinable public mutating func takeValue(_ value : Any?, forKey key: String) throws { // TODO: support the Int.Type key values below guard let k = key as? Key else { @@ -237,6 +260,7 @@ extension Dictionary: KeyValueCodingType /*, MutableKeyValueCodingType */ { self[k] = v } + @inlinable public func value(forKey k: String) -> Any? { if let k = k as? Key { guard let value : Value = self[k] else { return nil } @@ -257,6 +281,7 @@ extension Dictionary: KeyValueCodingType /*, MutableKeyValueCodingType */ { extension Array : KeyValueCodingType { // KVC on an array is a map operation. Except for the special '@' functions. + @inlinable public func value(forKey k: String) -> Any? { // Element if k.hasPrefix("@") { @@ -280,10 +305,12 @@ open class KeyValueCodingBox : KeyValueCodingTargetValue { public final var value : T - init(_ value : T) { + @inlinable + public init(_ value : T) { self.value = value } + @inlinable public func setValue(_ value: Any?) throws { // TODO: more type coercion if let v = value as? T {