[英]Choosing Database.Esqueleto SQL operators dynamically
I want to create Database.Esqueleto
queries dynamically based on data stored in the database (see DynamicQuery Database.Persist
entity in the code snippet below). 我想基于存储在数据库中的数据动态创建
Database.Esqueleto
查询(请参阅下面的代码片段中的DynamicQuery Database.Persist
实体)。 The code below compiles but it is not very elegant because of repeated definitions ( op
for Text field type, op2
for Day field type, and op3
for Bool
field type). 下面的代码可以编译,但由于重复的定义(对于Text字段类型为
op
,对于Day字段类型为op2
,对于Bool
字段类型为op3
,它不是很优雅。
Is it possible to write a more general function similar to op
that could be used in all cases in the definition of expr
? 是否有可能编写类似于
op
的更通用的函数,该函数可以在expr
的定义中的所有情况下使用?
Trying to reuse op
for the Day field type where op2
is used results in the following error message: 尝试对使用
op2
的Day字段类型重用op
会导致以下错误消息:
test.hs:68:46:
Couldn't match expected type `Text' with actual type `Day'
Expected type: EntityField (ItemGeneric backend0) Text
Actual type: EntityField (ItemGeneric backend0) Day
In the second argument of `(^.)', namely `ItemInserted'
In the first argument of `op', namely `(mp ^. ItemInserted)'
The code snippet follows: 该代码段如下:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
import Database.Esqueleto
import Database.Esqueleto.Internal.Sql
import Data.Time.Calendar
import Data.Text (Text)
import qualified Data.Text as T
import Database.Persist.TH
import Database.Persist.Sqlite hiding ((==.), (!=.), (>=.), (<=.))
import Control.Monad.IO.Class (liftIO)
import Enums
{- enumerated field types have to be in a separate module due to GHC
-- stage restriction. Enums.hs contains the following definitions:
{-# LANGUAGE TemplateHaskell #-}
module Enums where
import Database.Persist.TH
data DynField = DynFieldName | DynFieldInserted | DynFieldActive deriving (Eq, Read, Show)
derivePersistField "DynField"
data SqlBinOp = SqlBinOpLike | SqlBinOpLtEq | SqlBinOpGtEq | SqlBinOpNotEq | SqlBinOpEq deriving (Eq, Read, Show)
derivePersistField "SqlBinOp"
-}
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
DynamicQuery
field DynField
op SqlBinOp
value Text
Item
name Text
inserted Day
active Bool
|]
safeRead :: forall a. Read a => Text -> Maybe a
safeRead s = case (reads $ T.unpack s) of
[(v,_)] -> Just v
_ -> Nothing
getItems dc = do
select $ from $ \mp -> do
where_ $ expr mp
return $ mp ^. ItemId
where
value = dynamicQueryValue dc
boolValue = case safeRead value of
Just b -> b
Nothing -> False
dateValue = case safeRead value of
Just dt -> dt
Nothing -> fromGregorian 1900 1 1
expr = \mp -> case dynamicQueryField dc of
DynFieldName -> (mp ^. ItemName) `op` val value
DynFieldInserted -> (mp ^. ItemInserted) `op2` val dateValue
DynFieldActive -> (mp ^. ItemActive) `op3` val boolValue
op = case dynamicQueryOp dc of
SqlBinOpEq -> (==.)
SqlBinOpNotEq -> (!=.)
SqlBinOpGtEq -> (>=.)
SqlBinOpLtEq -> (<=.)
SqlBinOpLike -> unsafeSqlBinOp " ILIKE "
op2 = case dynamicQueryOp dc of
SqlBinOpEq -> (==.)
SqlBinOpNotEq -> (!=.)
SqlBinOpGtEq -> (>=.)
SqlBinOpLtEq -> (<=.)
SqlBinOpLike -> unsafeSqlBinOp " ILIKE "
op3 = case dynamicQueryOp dc of
SqlBinOpEq -> (==.)
SqlBinOpNotEq -> (!=.)
SqlBinOpGtEq -> (>=.)
SqlBinOpLtEq -> (<=.)
SqlBinOpLike -> unsafeSqlBinOp " ILIKE "
main = runSqlite ":memory:" $ do
runMigration migrateAll
_ <- insert $ Item "item 1" (fromGregorian 2014 2 11) True
_ <- insert $ Item "item 2" (fromGregorian 2014 2 12) False
let dc = DynamicQuery DynFieldName SqlBinOpEq "item 1"
items <- getItems dc
liftIO $ print items
Using the operators you gave on your example, it's just a matter of providing an explicit type signature. 使用您在示例中给出的运算符,只需提供一个明确的类型签名即可。 The following works fine:
以下工作正常:
expr = \mp -> case dynamicQueryField dc of
DynFieldName -> (mp ^. ItemName) `op` val value
DynFieldInserted -> (mp ^. ItemInserted) `op` val dateValue
DynFieldActive -> (mp ^. ItemActive) `op` val boolValue
op :: PersistField a => SqlExpr (Value a) -> SqlExpr (Value a) -> SqlExpr (Value Bool)
op = case dynamicQueryOp dc of
SqlBinOpEq -> (==.)
SqlBinOpNotEq -> (!=.)
SqlBinOpGtEq -> (>=.)
SqlBinOpLtEq -> (<=.)
SqlBinOpLike -> unsafeSqlBinOp " ILIKE "
If any of the operators had more constraints on its arguments (eg, Num a
), then the approach above would force the whole op
to have the union of all constraints. 如果任何一个运算符对其参数有更多的约束(例如
Num a
),则上述方法将迫使整个op
具有所有约束的并集。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.