Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better docs on how to write Projections #131

Open
Profpatsch opened this issue Aug 18, 2021 · 7 comments
Open

Better docs on how to write Projections #131

Profpatsch opened this issue Aug 18, 2021 · 7 comments

Comments

@Profpatsch
Copy link

The INSERT/UPSERT docs currently don’t describe how to write a Projection.

Following the type in the reference leads to

type Projection a b = Transpose (Field a) a -> Transpose (Field a) b 

where Transpose is an associated type of the Table class, which has over a dozen instances, so it’s very hard to figure out how a Projection would actually look like in practice.

@ocharles
Copy link
Contributor

Do you think attaching some examples to the Projection type would help? The intuition is it's a function on the columns in the table. For example:

fooAndBar :: Projection (MyTable Expr) (Expr Foo, Expr Bar)
fooAndBar MyTable{ foo, bar } = (foo, bar)

(I think that's right)

@Profpatsch
Copy link
Author

Do you think attaching some examples to the Projection type would help? The intuition is it's a function on the columns in the table. For example:

yes, I think there’s so many type classes that are just there for plumbing and subtyping, having examples of all the instances in the places that matter (e.g. concrete types for Projection) would really help.

But definitely also an example in the tutorial.

@Profpatsch
Copy link
Author

fooAndBar :: Projection (MyTable Expr) (Expr Foo, Expr Bar)
fooAndBar MyTable{ foo, bar } = (foo, bar)

This definitely doesn’t work for me. First of all, it infers a MyTable Name and I’m pretty sure it also doesn’t simply allow using the fields in a tuple.

This will need a bunch more documentation, nobody can figure out what

class (Transposes (Context a) (Field a) a (Transpose (Field a) a), Transposes (Context a) (Field a) b (Transpose (Field a) b)) => Projecting a b 

means without a few good examples and some semantics.

@Profpatsch
Copy link
Author

Staring at the types a bit more, I inferred that I need something from type MyTable (c :: Context) -> ??

?? naively is MyTable (c2 :: Context), but I have no idea how that is a projection if I have to give it all fields.

There is some Transformation and Rep shenanigans going on, but for the life of me I can’t figure out how that translates to a tuple of fields.

What speaks against just making the type of Projection :: (MyTable Expr -> MyTable (Const Bool)) or something along that spirit, so for every field you can tell whether it should be taken into account by setting the field to Const True?

@ocharles
Copy link
Contributor

In lieu of any documentation, can you share some code you have and what you want? I'm happy to help you out with that just to unblock you!

@Profpatsch
Copy link
Author

Profpatsch commented Aug 19, 2021

I fell back to Hasql and wrote this statement:

Hasql.Trans.statement
                ( translationKeyId,
                  translationLanguage,
                  translationTranslation
                )
                $ do
                  let sql =
                        [Embed.embedVerbatimBytes|
                     INSERT INTO translation_service.translations
                     (keyid, language, translation)
                     VALUES ($1, $2, $3)
                     -- Every key/language combo can just appear once.
                     -- if there is a conflict, we just override the entry
                     -- with the new translation.
                     ON CONFLICT (keyid, language)
                     DO UPDATE
                     SET
                     translation = EXCLUDED.translation
                   |]
                  let encoder =
                          ( contrazip3
                              (Enc.param (Enc.nonNullable Enc.int8))
                              (Enc.param (Enc.nonNullable Enc.text))
                              (Enc.param (Enc.nonNullable Enc.text))
                          )

The code I mocked up with Rel8 (except for the upsert):

data TranslationTable f = TranslationTable
 { -- translationId :: Column f TranslationId,
   translationKeyId :: Column f KeyId,
   translationLanguage :: Column f DbLanguageTag,
   translationTranslation :: Column f DbTranslatedString
 }
 deriving stock (Generic)
 deriving anyclass (Rel8able)

insert $
 Insert
   { into = translationSchema,
     rows =
       ( do
           pure $
             TranslationTable
               { translationKeyId = lit translationKeyId,
                 translationLanguage = lit (at & addTranslationLanguage),
                 translationTranslation = lit (at & addTranslationTranslation)
               }
       ),
     -- if there is a conflict, we just override the entry
     onConflict =
       DoUpdate $
         let index :: TranslationTable Expr -> TranslationTable Expr = \tt -> tt
          in Upsert
               -- TODO: can’t get the UPSERT index working,
               -- see https://github.com/circuithub/rel8/issues/131
               { index = id, -- \(TranslationTable {translationKeyId, translationLanguage}) -> (translationKeyId, translationLanguage),
                 set = \_old new -> new,
                 updateWhere = \_ _ -> lit True
               },
     returning = pure ()
   }

So basically just overwrite the columns with the new set.

@ocharles
Copy link
Contributor

Ok, I think something has broken at some point, because what should work no longer does... Sorry you've ran into this - you're not holding it wrong, we just gave you something broken!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants