Осталось присоединиться в Опалее

Я пытался запустить левое объединение, используя Opaleye в проекте, но я не смог заставить код скомпилировать. Я начну с двух "моделей", которые представляют связанные таблицы:

Первый:

data ModelA' a b = Model { primA :: a, foreignA :: b }
type ModelA = ModelA' UUID UUID
type ModelAColumn = ModelA' (Column PGUuid) (Column (Nullable PGUuid))

$(makeAdaptorAndInstance "pModelA" ''ModelA')

table :: Table ModelAColumn ModelAColumn
table = Opaleye.table "model_a" $ pModelA (ModelA (tableColumn "uuid") (tableColumn "foreign"))

А также:

data ModelB' a b = Model { primB :: a, valB :: b }
type ModelB = ModelB' UUID String
type ModelBColumn = ModelB' (Column PGUuid) (Column PGText)

$(makeAdaptorAndInstance "pModelB" ''ModelB')

table :: Table ModelBColumn ModelBColumn
table = Opaleye.table "model_b" $ pModelB (ModelB (tableColumn "uuid") (tableColumn "val"))

Как показывают типы, ModelA может не иметь привязки ModelB.

Мне нужен запрос для получения пар (ModelA, может быть ModelB), заданных левым соединением между таблицами ForeignA == primB. Я ожидал, что это будет выглядеть так:

doJoin :: Connection -> IO [(ModelA, Maybe ModelB)]
doJoin conn = runQuery conn query
  where
    query :: Query (ModelAColumn, Maybe ModelBColumn)
    query = leftJoin (queryTable ModelA.table) (queryTable ModelB.table) (\(ma, mb) -> foreignA ma .== primB mb)

Но это не работает. Я также попробовал несколько вариантов, в частности я заменил сигнатуру типа в запросе, чтобы явно указать обнуляемость столбцов справа:

query :: Query (ModelAColumn, (Column (Nullable PGUuid), Column (Nullable PGText))

Но это не так с:

Нет экземпляра для Data.Profunctor.Product.Default.Class.Default Opaleye.Internal.Join.NullMaker ModelBColumn (Column (Nullable PGUuid), Column (Nullable PGText).

Как я могу сделать этот запрос в Опалее?

4 ответа

Решение

Здесь есть несколько недоразумений. Я создал полную рабочую версию ниже.

Во-первых, тип возвращаемого значения leftJoin не является

Query (ModelAColumn, Maybe ModelBColumn)

Ты должен сделать

type ModelBNullableColumn = ModelB' (Column (Nullable PGUuid))
                                    (Column (Nullable PGText))

а затем использовать

Query (ModelAColumn, ModelBNullableColumn)

Во-вторых, тип возвращаемого значения runQuery не является

IO [(ModelA, Maybe ModelB)]

Ты должен сделать

type ModelBMaybe = ModelB' (Maybe UUID) (Maybe String)

и использовать

IO [(ModelA, ModelBMaybe)]

Причина этих различий заключается в том, что Nullable а также Maybe должны применяться непосредственно к каждому столбцу и значению в ModelBColumn а также ModelB не к ценностям в целом.

(Есть также некоторые странные синтаксические ошибки, такие как

ModelA { tableColumn "uuid", tableColumn "foreign" }

Это означает, что ваш код не имеет надежды на компиляцию. Я тоже это исправил.)

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

import           Opaleye hiding (table)
import qualified Opaleye
import Data.Profunctor.Product.TH
import Database.PostgreSQL.Simple hiding (Query)
import Data.UUID

data ModelA' a b = ModelA { primA :: a, foreignA :: b }
type ModelA = ModelA' UUID (Maybe UUID)
type ModelAColumn = ModelA' (Column PGUuid) (Column (Nullable PGUuid))

$(makeAdaptorAndInstance "pModelA" ''ModelA')

modelAtable :: Table ModelAColumn ModelAColumn
modelAtable = Opaleye.table "model_a" $ pModelA ModelA { primA = tableColumn "uuid", foreignA = tableColumn "foreign" }

data ModelB' a b = ModelB { primB :: a, valB :: b }
type ModelB = ModelB' UUID String
type ModelBMaybe = ModelB' (Maybe UUID) (Maybe String)
type ModelBColumn = ModelB' (Column PGUuid) (Column PGText)
type ModelBNullableColumn = ModelB' (Column (Nullable PGUuid)) (Column (Nullable PGText))

$(makeAdaptorAndInstance "pModelB" ''ModelB')

modelBtable :: Table ModelBColumn ModelBColumn
modelBtable = Opaleye.table "model_b" $ pModelB ModelB { primB = tableColumn "uuid", valB = tableColumn "val" }

doJoin :: Connection -> IO [(ModelA, ModelBMaybe)]
doJoin conn = runQuery conn query
  where
    query :: Query (ModelAColumn, ModelBNullableColumn)
    query = leftJoin (queryTable modelAtable) (queryTable modelBtable) (\(ma, mb) -> matchNullable (pgBool False) (.== primB mb) (foreignA ma))

main :: IO ()
main = return ()

Поскольку этот вопрос был задан и на него был дан ответ, в Opaleye был представлен гораздо более простой способ выполнения левых соединений. Проверьте функцию под названиемoptional.

Вам нужно использовать Functional Joinконкретно leftJoinF,

Проверьте это https://hackage.haskell.org/package/opaleye-0.5.4.0/docs/Opaleye-FunctionalJoin.html

Вам нужно будет предоставить результаты выбора в ваших двух таблицах в качестве 4-го и 5-го аргументов. Эквивалент foreignA == primB в качестве третьего аргумента.

Для вашего второго аргумента вы должны будете указать некоторое значение по умолчанию, которое должно использоваться всякий раз, когда Maybe ModelB быть возвращенным Nothing

Попытка изменить следующее:

query :: Query (ModelAColumn, Maybe ModelBColumn)

в

query :: Query ((Column PGUuid) (Column (Nullable PGUuid))
               , (Column (Nullable PGUuid)) (Column (Nullable PGText)))

Попробуй заставить это проверить тип БЕЗ звонка runQuery в теме. Как только это сработает, вернитесь к остальной части решения.

Другие вопросы по тегам