in AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/Support/Model+Compare.swift [31:146]
func compare(_ model1: Model, _ model2: Model) -> Bool {
let modelType1 = ModelRegistry.modelType(from: model1.modelName)
let modelType2 = ModelRegistry.modelType(from: model2.modelName)
if modelType1 != modelType2 {
// no need to compare models as they have different type
return false
}
for (fieldName, modelField) in fields {
// read only fields or fields updated from the service are skipped for equality check
// examples of such fields include `createdAt`, `updatedAt` and `coverId` in `Record`
if modelField.isReadOnly || ModelSchema.serviceUpdatedFields.contains(modelField.name) {
continue
}
let value1 = model1[fieldName] ?? nil
let value2 = model2[fieldName] ?? nil
// check equality for different `ModelFieldType`
switch modelField.type {
case .string:
guard let value1Optional = value1 as? String?, let value2Optional = value2 as? String? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .int:
guard let value1Optional = value1 as? Int?, let value2Optional = value2 as? Int? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .double:
guard let value1Optional = value1 as? Double?, let value2Optional = value2 as? Double? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .date:
guard let value1Optional = value1 as? Temporal.Date?,
let value2Optional = value2 as? Temporal.Date? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .dateTime:
guard let value1Optional = value1 as? Temporal.DateTime?,
let value2Optional = value2 as? Temporal.DateTime? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .time:
guard let value1Optional = value1 as? Temporal.Time?,
let value2Optional = value2 as? Temporal.Time? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .timestamp:
guard let value1Optional = value1 as? String?, let value2Optional = value2 as? String? else {
return false
}
if !compare(value1Optional, value2Optional) {
return false
}
case .bool:
guard let value1Optional = value1 as? Bool?, let value2Optional = value2 as? Bool? else {
return false
}
if !compare(value1Optional?.intValue, value2Optional?.intValue) {
return false
}
case .enum:
guard case .some(Optional<Any>.some(let value1Optional)) = value1,
case .some(Optional<Any>.some(let value2Optional)) = value2 else {
if value1 == nil && value2 == nil {
continue
}
return false
}
let enumValue1Optional = (value1Optional as? EnumPersistable)?.rawValue
let enumValue2Optional = (value2Optional as? EnumPersistable)?.rawValue
if !compare(enumValue1Optional, enumValue2Optional) {
return false
}
case .embedded, .embeddedCollection:
do {
if let encodable1 = value1 as? Encodable,
let encodable2 = value2 as? Encodable {
let json1 = try SQLiteModelValueConverter.toJSON(encodable1)
let json2 = try SQLiteModelValueConverter.toJSON(encodable2)
if !compare(json1, json2) {
return false
}
}
} catch {
continue
}
// only the first level of data is used for comparison of models
// and deeper levels(associated models/connections) are ignored
// e.g. The graphql request contains only the information needed in the graphql variables which is sent to
// the service. In such a case, the request model may have multiple levels of data while the response
// model will have just one.
case .model, .collection:
continue
}
}
return true
}