Skip to content

A fragment read back from The Graph doesn't match (hash) one created in memory #3579

@potrebic

Description

@potrebic

Summary

Summary:
It appears that equality and hasing is inconsistent between model built in memory versus models hydrated from the cahe.

Details:
Here’s a tiny example, using some small object defined in our schema:

type FolderItemConnectionEdge implements EdgeInterface {
   cursor: String
   node: Item!
}

And here’s a fragment using the above type

fragment folderItemEdge on FolderItemConnectionEdge @apollo_client_ios_localCacheMutation {
    id: cursor      
}

Creating 2 instances of the semantically same FolderItemEdge model in memory (e.g. FolderItemEdge(id: "File:15"), and the instances behave has expected. If the id's are the same the instances are considered equal. However, if you read an instance out of The Graph/cache, that instance will not equate with the in memory instances.

Version

1.22

Steps to reproduce the behavior

So here’s some swift code. First confirming that in-memory objects equate/hash as expected

        let e1 = FolderItemEdge(id: "File:15")
        let e2 = FolderItemEdge(id: "File:15")
        print(e1 == e2)         // -> true

        var uniqueEdges = Set<FolderItemEdge>([])
        print("mergeEdges (e1): \(uniqueEdges.insert(e1))")
        print("mergeEdges (e2): \(uniqueEdges.insert(e2))")
        // uniqueEdges only has 1 entry, since both e1 and e2 are the same

And now I save an instance to The Graph. And read it back out... things are broken.

        let key = "FolderItemConnectionEdge:File:15"
        // Write the `FolderItemEdge(id: "File:15")` object to The Graph
        apolloClient.store.withinReadWriteTransaction { transaction in
            do {
                try transaction.write(selectionSet: e1, withKey: key)
            } catch {
            }
        }

        // Read the `FolderItemEdge(id: "File:15")` out of The Graph
        apolloClient.store.withinReadTransaction { transaction in
            let d1 = try? transaction.readObject(ofType: FolderItemEdge.self, withKey: key)
            if let d1 {
                // And evidently... they are now different - they don't hash well together!
                print("equality: \(e1 == d1)")                                  // This fails
                print("hash (d1): \(uniqueEdges.insert(d1))")      // `d1` is added to the set!
                print("set count: \(uniqueEdges.count)")            // and the set contains 2 items (not 1)!
            }
        }

The equality check fails. e1 != d1???

Digging deeper in the debug I found this:

po lhs.__data._data["__typename"] == rhs.__data._data["__typename"]
==> true

po lhs.__data._data["id"] == rhs.__data._data["id"]
==> false

So something about the id's is different
Here’s a dump of the DataDicts:

(lldb) p e1.__data._data
([String : AnyHashable]) 2 key/value pairs {
  [0] = {
    key = "__typename"
    value = {
      _box = (_baseHashable = "FolderItemConnectionEdge")
    }
  }
  [1] = {
    key = "id"
    value = {
      _box = {
        _baseHashable = "File:15"
      }
    }
  }
}

(lldb) p d1.__data._data
([String : AnyHashable]) 2 key/value pairs {
  [0] = {
    key = "__typename"
    value = {
      _box = (_baseHashable = "FolderItemConnectionEdge")
    }
  }
  [1] = {
    key = "id"
    value = {
      _box = (_baseHashable = "File:15")
    }
  }
}

I noted that the “value” of the “id” key prints slightly differently. Here’s e1 which spans multiple lines with brackets { and }

_box = {
        _baseHashable = "File:15"
      }

vs all on 1 line for d1 using parens.

      _box = (_baseHashable = "File:15")

Logs

Anything else?

No response

Metadata

Metadata

Assignees

Labels

bugGenerally incorrect behavior

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions