bitemyapp / esqueleto

New home of Esqueleto, please file issues so we can get things caught up!
BSD 3-Clause "New" or "Revised" License
376 stars 108 forks source link

Cannot insert `bigint[]` value with `upsert` #395

Closed rinn7e closed 5 months ago

rinn7e commented 5 months ago

Hi everyone, I have a simple problem, hope someone could help.

I have this table:

CreateTable
      { name = "group_marks"
      , schema =
        [ Column "profile_ids" (SqlOther "bigint[]") [NotNull]
        , Column "contain_list" SqlBool [NotNull]
        , Column "mark" SqlString [NotNull]
        ]
      , constraints =
        [ PrimaryKey ["profile_ids", "contain_list"]
        ]
      }

type:

  GroupMarks sql=group_marks
    profileIds (PgList Int64)
    containList Bool
    mark GroupMark
    deriving Eq Ord Show

    Primary profileIds containList

Since I want to use postgres array (not json array), I have this new type PgList:

newtype PgList a = PgList {unPgList :: [a]}

...

instance (PersistField a) => PersistField (PgList a) where
  toPersistValue (PgList a) = PersistArray . fmap toPersistValue $ a
  fromPersistValue (PersistArray l) = PgList <$> fromPersistList l
  fromPersistValue (PersistList l) = PgList <$> fromPersistList l
  fromPersistValue (PersistNull) = Right $ PgList []

When I try to upsert a row, I got this error:

upsert (GroupMarks (PgList firstKey) containList newMark) [ GroupMarksMark Persist.=. newMark ]

Error:

[Debug#SQL] INSERT INTO "group_marks"("profile_ids","contain_list","mark") VALUES (?,?,?) ON CONFLICT ("profile_ids","contain_list") DO UPDATE SET "mark"=? WHERE "group_marks"."profile_ids" =? AND "group_marks"."contain_list" =? RETURNING "group_marks"."profile_ids", "group_marks"."contain_list", "group_marks"."mark"; [PersistArray [PersistInt64 1,PersistInt64 2],PersistBool False,PersistText "Spam",PersistText "Spam",PersistArray [PersistInt64 1,PersistInt64 2],PersistBool False]

SqlError {sqlState = "42883", sqlExecStatus = FatalError, sqlErrorMsg = "operator does not exist: bigint[] = integer[]", sqlErrorDetail = "", sqlErrorHint = "No operator matches the given name and argument types. You might need to add explicit type casts."}

If I use SqlOther "integer[]" for profile_ids, everything works fine. But technically, it should be bigint for Int64.

operator does not exist: bigint[] = integer[]

I guess, if there is an explicit way to tell upsert to use bigint instead of integer, it should work. Anyone has any idea on how to do this.

rinn7e commented 5 months ago

I just realized that upsert is from persistent so maybe it's more fit to write an issue in persistent repo instead.

Anyway, a workaround is to insert or update explicitly:

    -- if no existing record
      insert_ (GroupMarks (PgList firstKey) containList newMark)
    -- if there is an existing record
      update \gm -> do
        set gm [ GroupMarksMark =. val newMark ]
        where_ $ cond gm