Fork me on GitHub
#pathom
<
2022-11-10
>
Mark Wardle13:11:36

Hi all. Experimenting with union resolvers and queries. I've made it work using a nested property but think that this nesting is redundant, and wondered whether I could get it to work at the top-level.

Mark Wardle13:11:03

Here is a resolver that works:

(pco/defresolver component-by-id
  "Resolve an arbitrary SNOMED URI as per "
  [{::keys [svc]} {:info.snomed/keys [id]}]
  {::pco/output [{:info.snomed/component {:info.snomed.Concept/id [:info.snomed.Concept/id]
                                         :info.snomed.Description/id [:info.snomed.Description/id]
                                         :info.snomed.Relationship/id [:info.snomed.Relationship/id]
                                         :info.snomed.RefsetItem/id [:info.snomed.RefsetItem/id]}}]}
  {:info.snomed/component
   (cond
     (number? id) (case (snomed/identifier->type id)
                    :info.snomed/Concept {:info.snomed.Concept/id id}
                    :info.snomed/Description {:info.snomed.Description/id id}
                    :info.snomed/Relationship {:info.snomed.Relationship/id id}
                    nil)
     (uuid? id) {:info.snomed.RefsetItem/id id}
     (string? id) {:info.snomed.RefsetItem/id (parse-uuid id)})})
I can pass in an arbitrary :info.snomed/id and the identifier is parsed, the type determined, and a concrete result returned in the property :info.snomed/component. This means that this query returns the correct value:
(p.eql/process registry
               {:info.snomed/id "7c0d7d61-c571-5bf9-9329-fdbfee8747d0"}
               [{:info.snomed/component
                 {:info.snomed.Concept/id     [{:info.snomed.Concept/preferredDescription [:info.snomed.Description/term]}]
                  :info.snomed.Description/id [:info.snomed.Description/term]
                  :info.snomed.Relationship/id [:info.snomed.Relationship/id]
                  :info.snomed.RefsetItem/id [:info.snomed.RefsetItem/id :info.snomed.RefsetItem/referencedComponentId]}}])
Result: #:info.snomed{:component #:info.snomed.RefsetItem{:id #uuid"7c0d7d61-c571-5bf9-9329-fdbfee8747d0",:referencedComponentId 123558018}}

Mark Wardle13:11:45

But is it really necessary to nest this union within a property :info.snomed/component? I have tried to do this union query without nesting within that property, but get a result that looks more like EQL back: (p.eql/process registry {:info.snomed/id 80146002} {:info.snomed.Concept/id [:info.snomed.Concept/id {:info.snomed.Concept/preferredDescription [:info.snomed.Description/term]}] :info.snomed.Description/id [:info.snomed.Description/id]}) => {[:info.snomed.Concept/id [:info.snomed.Concept/id #:info.snomed.Concept{:preferredDescription [:info.snomed.Description/term]}]] #:info.snomed.Concept{:id [:info.snomed.Concept/id #:info.snomed.Concept{:preferredDescription [:info.snomed.Description/term]}]}, [:info.snomed.Description/id [:info.snomed.Description/id]] #:info.snomed.Description{:id [:info.snomed.Description/id]}}

Mark Wardle13:11:33

Alternative is to ignore union queries, and request all properties and only return the valid ones for that type?

dvingo21:11:40

This doesn't look like the typical union query setup - where you have a heterogeneous collection of things and each member of the collection has a unique attribute that is used for its id property. In your example, the value of the id attribute is used to determine the polymorphism instead

dvingo21:11:16

you probably already saw, but just for completeness https://pathom3.wsscode.com/docs/eql#union-queries describes the typical problem

Mark Wardle21:11:51

Thanks @U051V5LLP I think it is a job for a union query because resolving on :info.snomed/id could result in a concept, description, relationship or refset-item. We can determine type based on the ID and its penultimate 2 digits, or if a UUID or string. It works, but only as a nested property, not a more flattened structure.

Mark Wardle21:11:42

But maybe I should just accept some attributes will be unreachable depending on type, and always use lenient mode. But I thought unions were designed to solve this. Nesting isn’t so bad but I wonder whether I am either not declaring a top level union, or using the right query if indeed a top level union is possible.

dvingo21:11:27

But the branching in your case is on the value of the ID attribute, not the name of the Id attribute - that's how it differs from the usual union use-case. Does that make sense? My understanding of what you want to achieve is that yes, the resolver will declare all possible fields and for some of those entities certain fields won't be available

👍 1
Mark Wardle22:11:43

Yes that does make sense now. Thank you. I will make it simpler!

Mark Wardle11:11:59

So if I am strict mode, the recommended approach is to make all attributes in my query optional? ie [(pco/? :info.snomed.Concept/id) ...] , or using lenient mode and ignoring unresolved properties, rather than using a union query which necessitates a nested property?

dvingo15:11:32

I am not sure about what is recommended, but I think either strict or lenient would work - then it's up to the consumer of the data to detect the available fields

Mark Wardle19:11:40

Thanks. This is working, even though for my use case, it forces lenient mode or a lot of declaring attributes to all be optional. I guess what I was looking for was a union-like thing in which I can declare multiple sets of attributes that can be provided depending on the request at hand, but at the top-level. Thank you for your advice.

Eric Dvorsak16:11:07

the issue definitely seems to be coming from the index-io

Eric Dvorsak16:11:17

since it doesn't ref nested attributes it looks like this:

:index-io
                                      {#{} {:parent #:child{:id {}}},
                                       #{:parent} {:child {}},
                                       #{:child/id} {:child {}}}

caleb.macdonaldblack18:11:11

I’m expecting the result of parent-child with :child/id to be used to resolve the value of child-by-id and then that to be resolved by parent->child.

Eric Dvorsak19:11:15

Yeah that's what I figured and the planner is stucked in a recursive loop on the parent->child resolver

caleb.macdonaldblack21:11:04

So it should be getting picked up in there, but isn’t.

Eric Dvorsak14:11:28

Would be interesting to make functioning resolvers for this example, I'm not sure what child value could be in this scenario

sheluchin23:11:33

Does Pathom have anything for dealing with data-driven assignment?

dehli01:11:23

what do you mean by data-driven assignment?

sheluchin10:11:48

That's when data is structured using some of the data as the key.

[{:person/first-name "linus"
  :person/last-name "torvalds"
  :person/famous-for "linux"}
 {:person/first-name "rich"
  :person/last-name "hickey"
  :person/famous-for "clojure"}]

{"linus" {:person/last-name "torvalds"
          :person/famous-for "linux"}
 "rich" {:person/last-name "hickey"
         :person/famous-for "clojure"}}

wilkerlucio12:11:42

@UPWHQK562 can you tell more on what you are trying to do? this map looks a simple lookup by name

sheluchin19:11:32

@U066U8JQJ I don't have a particular application for it right now but it is a fairly common pattern that some REST APIs return their data in. It is a simple lookup by name, except it could be any number of keys and you may not know them. I recall having to massage some data that was given to me like this to make it work well with Pathom, and I was just wondering if this is a common enough pattern that Pathom has some utils for dealing with it.

wilkerlucio19:11:16

you can check the built in resolvers, there are a couple helpers to deal with this format, but mostly for static data (vs dynamic data via api), but it can be a source of inspiration for you to implement dynamic ones

sheluchin12:11:40

Thanks, @U066U8JQJ. That connection was not obvious to me, but I think I understand it now.