tomjaguarpaw / haskell-opaleye

Other
601 stars 115 forks source link

How to use a primary key newtype wrapper with an aggregation #479

Open uliSchuster opened 4 years ago

uliSchuster commented 4 years ago

In a question I posted on Stackoverflow, I used a rather complicated expression

OE.aggregate (PP.p2 (OE.groupBy, OE.arrayAgg)) $
    arr (first) <<< articleTagNamesQ

to create an arrow for an aggregation. In his reply, @tomjaguarpaw pointed out that the expression can be greatly simplified. However, in my real code here at line 226 (not the simplified SO example), the simplification does not work. The problem seems to be that I polymorphically newtype the primary key:

newtype ArticleIdT a = ArticleId {getArticleId :: a}
  deriving (Eq, Show, Display)
$(makeAdaptorAndInstance "pArticleId" ''ArticleIdT)
type ArticleIdField = ArticleIdT (F OE.SqlInt8)
type OptionalArticleIdField = ArticleIdT (Maybe (F OE.SqlInt8))
type ArticleId = ArticleIdT Int64

See line 52ff.

However, I was not able to formulate the aggregation in terms of this newtyped primary key, but had to resort to the raw SQL type SqlInt8. As a consequence, I had to unwrap the primary key newtype wrapper, which in turn led to the complex expression mentioned above. What would be the proper way to maintain the newtype wrapper throughout the queries?

tomjaguarpaw commented 4 years ago

Hello Uli, thanks for coming here to ask! I believe you want the following (although I haven't compiled it to type check it). Please let me know if it works.

articleTagsQ :: OE.Select (PA.ArticleIdField, F (OE.SqlArray OE.SqlText))
articleTagsQ =
  OE.aggregate (PP.p2 (pArticleId OE.groupBy, OE.arrayAgg)) articleTagNamesQ
uliSchuster commented 4 years ago

Dear Tom, thanks for the quick reply! Unfortunately, I could not get your suggestion to type-check. Here is the resulting error:

• Couldn't match expected type ‘PA.ArticleIdT (OE.Aggregator (OE.Column OE.PGInt8) (OE.Column OE.PGInt8))’
with actual type ‘OE.Aggregator (OE.Column a0) (OE.Column a0)’
• In the first argument of ‘PA.pArticleId’, namely ‘OE.groupBy’
     In the expression: PA.pArticleId OE.groupBy
     In the first argument of ‘PP.p2’, namely ‘(PA.pArticleId OE.groupBy, OE.arrayAgg)’

I suppose my problem is that I'm lacking a deeper understanding of product profunctors. Can you point me to some documentation to get a better grasp of it?

tomjaguarpaw commented 4 years ago

Yes, I beg your pardon, it should be as below. There isn't really any documentation to get a better grasp of product profunctors! This is one reason why it's helpful for you to ask your questions on this issue tracker: so I can know what sort of documentation I need to write!

articleTagsQ :: OE.Select (PA.ArticleIdField, F (OE.SqlArray OE.SqlText))
articleTagsQ =
  OE.aggregate (PP.p2 (pArticleId (ArticleIdT OE.groupBy), OE.arrayAgg)) articleTagNamesQ
tomjaguarpaw commented 4 years ago

By the way, the pattern that's occurring here is the same as the pattern occurring in your code linked below, except you are using p2 of a pair here and pArticle of an Article below.

https://github.com/uliSchuster/real-world-backend/blob/master/conduit-persistence/src/Conduit/Persistence/ArticlesTable.hs#L123-L136

uliSchuster commented 4 years ago

Thanks! Now the type checker is happy - with a small change: It must be the concrete ArticleId instead of the polymorphic ArticleIdT.

tomjaguarpaw commented 4 years ago

Ah, I see. The constructor is named ArticleId. Great!