3

我一直在尝试在项目中使用 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

我需要一个查询来获取由foreignA == primB上的表之间的左连接给出的(ModelA,Maybe ModelB)对。我期待它看起来像:

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 没有实例(列(可空 PGUuid),列(可空 PGText)。

如何在 Opaleye 中进行此查询?

4

3 回答 3

3

这里有几个误解。我在下面制作了一个完整的工作版本。

首先,返回类型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必须直接应用于其中的每一列和值,ModelBColumnModelB不是应用于整个值。

(还有一些奇怪的语法错误,比如

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 ()
于 2017-10-21T15:15:04.460 回答
0

尝试更改以下内容:

query :: Query (ModelAColumn, Maybe ModelBColumn)

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

尝试让它在不调用runQuery它的情况下进行类型检查。一旦可行,请返回解决方案的其余部分。

于 2017-10-21T13:50:47.327 回答
0

您需要使用一个Functional Join,特别是leftJoinF.

看看这个https://hackage.haskell.org/package/opaleye-0.5.4.0/docs/Opaleye-FunctionalJoin.html

您需要在两个表上提供选择的结果作为它的第 4 和第 5 个参数。相当于foreignA == primB第三个参数。

对于您的第二个参数,您必须指定一些默认值,只要Maybe ModelB返回的是Nothing

于 2017-10-21T14:00:46.587 回答