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/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
}