{-# LANGUAGE BangPatterns, TypeFamilies, DataKinds, PolyKinds, TypeApplications, ScopedTypeVariables, ConstraintKinds, TypeOperators, GADTs, UndecidableInstances, StandaloneDeriving, FunctionalDependencies, FlexibleContexts, InstanceSigs, AllowAmbiguousTypes, DeriveAnyClass #-}
{-|
Module: IHP.QueryBuilder.Types
Description: Core data types for the QueryBuilder
Copyright: (c) digitally induced GmbH, 2020
-}
module IHP.QueryBuilder.Types
( -- * Core Types
  QueryBuilder (..)
, SQLQuery (..)
, Condition (..)
, ConditionValue (..)
, OrderByClause (..)
, OrderByDirection (..)
, FilterOperator (..)
, MatchSensitivity (..)
  -- * Helpers
, addCondition
, qualifyAndJoinColumns
  -- * Type Classes
, DefaultScope (..)
, EqOrIsOperator (..)
, FilterPrimaryKey (..)
) where

import IHP.Prelude
import IHP.ModelSupport
import IHP.HSX.Markup (ToHtml(..))
import qualified Control.DeepSeq as DeepSeq
import qualified GHC.Generics
import qualified Hasql.Encoders as Encoders
import qualified Prelude
import qualified Data.Text as Text

-- | Represents whether string matching should be case-sensitive or not
data MatchSensitivity = CaseSensitive | CaseInsensitive deriving (Int -> MatchSensitivity -> ShowS
[MatchSensitivity] -> ShowS
MatchSensitivity -> String
(Int -> MatchSensitivity -> ShowS)
-> (MatchSensitivity -> String)
-> ([MatchSensitivity] -> ShowS)
-> Show MatchSensitivity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MatchSensitivity -> ShowS
showsPrec :: Int -> MatchSensitivity -> ShowS
$cshow :: MatchSensitivity -> String
show :: MatchSensitivity -> String
$cshowList :: [MatchSensitivity] -> ShowS
showList :: [MatchSensitivity] -> ShowS
Show, MatchSensitivity -> MatchSensitivity -> Bool
(MatchSensitivity -> MatchSensitivity -> Bool)
-> (MatchSensitivity -> MatchSensitivity -> Bool)
-> Eq MatchSensitivity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MatchSensitivity -> MatchSensitivity -> Bool
== :: MatchSensitivity -> MatchSensitivity -> Bool
$c/= :: MatchSensitivity -> MatchSensitivity -> Bool
/= :: MatchSensitivity -> MatchSensitivity -> Bool
Eq)

-- | Operators used in WHERE clause conditions
data FilterOperator
    = EqOp -- ^ @col = val@
    | NotEqOp -- ^ @col != val@
    | InOp -- ^ @col IN (set)@
    | NotInOp -- ^ @col NOT IN (set)@
    | IsOp -- ^ @col IS val@
    | IsNotOp -- ^ @col IS NOT val@
    | LikeOp !MatchSensitivity -- ^ @col LIKE val@
    | NotLikeOp !MatchSensitivity -- ^ @col NOT LIKE val@
    | MatchesOp !MatchSensitivity -- ^ @col ~ pattern@
    | GreaterThanOp -- ^ @col > val@
    | GreaterThanOrEqualToOp -- ^ @col >= val@
    | LessThanOp -- ^ @col < val@
    | LessThanOrEqualToOp -- ^ @col <= val@
    | SqlOp -- ^ Used by 'filterWhereSql'
    deriving (Int -> FilterOperator -> ShowS
[FilterOperator] -> ShowS
FilterOperator -> String
(Int -> FilterOperator -> ShowS)
-> (FilterOperator -> String)
-> ([FilterOperator] -> ShowS)
-> Show FilterOperator
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FilterOperator -> ShowS
showsPrec :: Int -> FilterOperator -> ShowS
$cshow :: FilterOperator -> String
show :: FilterOperator -> String
$cshowList :: [FilterOperator] -> ShowS
showList :: [FilterOperator] -> ShowS
Show, FilterOperator -> FilterOperator -> Bool
(FilterOperator -> FilterOperator -> Bool)
-> (FilterOperator -> FilterOperator -> Bool) -> Eq FilterOperator
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FilterOperator -> FilterOperator -> Bool
== :: FilterOperator -> FilterOperator -> Bool
$c/= :: FilterOperator -> FilterOperator -> Bool
/= :: FilterOperator -> FilterOperator -> Bool
Eq)

-- | Represents an ORDER BY clause component
data OrderByClause =
    OrderByClause
    { OrderByClause -> Text
orderByColumn :: !Text
    , OrderByClause -> OrderByDirection
orderByDirection :: !OrderByDirection }
    deriving (Int -> OrderByClause -> ShowS
[OrderByClause] -> ShowS
OrderByClause -> String
(Int -> OrderByClause -> ShowS)
-> (OrderByClause -> String)
-> ([OrderByClause] -> ShowS)
-> Show OrderByClause
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> OrderByClause -> ShowS
showsPrec :: Int -> OrderByClause -> ShowS
$cshow :: OrderByClause -> String
show :: OrderByClause -> String
$cshowList :: [OrderByClause] -> ShowS
showList :: [OrderByClause] -> ShowS
Show, OrderByClause -> OrderByClause -> Bool
(OrderByClause -> OrderByClause -> Bool)
-> (OrderByClause -> OrderByClause -> Bool) -> Eq OrderByClause
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: OrderByClause -> OrderByClause -> Bool
== :: OrderByClause -> OrderByClause -> Bool
$c/= :: OrderByClause -> OrderByClause -> Bool
/= :: OrderByClause -> OrderByClause -> Bool
Eq, (forall x. OrderByClause -> Rep OrderByClause x)
-> (forall x. Rep OrderByClause x -> OrderByClause)
-> Generic OrderByClause
forall x. Rep OrderByClause x -> OrderByClause
forall x. OrderByClause -> Rep OrderByClause x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. OrderByClause -> Rep OrderByClause x
from :: forall x. OrderByClause -> Rep OrderByClause x
$cto :: forall x. Rep OrderByClause x -> OrderByClause
to :: forall x. Rep OrderByClause x -> OrderByClause
GHC.Generics.Generic, OrderByClause -> ()
(OrderByClause -> ()) -> NFData OrderByClause
forall a. (a -> ()) -> NFData a
$crnf :: OrderByClause -> ()
rnf :: OrderByClause -> ()
DeepSeq.NFData)

-- | The QueryBuilder is a flat newtype over SQLQuery. Each combinator directly
-- modifies fields of the underlying SQLQuery, avoiding any recursive tree traversal.
newtype QueryBuilder (table :: Symbol) = QueryBuilder { forall (table :: Symbol). QueryBuilder table -> SQLQuery
unQueryBuilder :: SQLQuery }

-- | Add a WHERE condition to a QueryBuilder, ANDing with any existing condition.
addCondition :: Condition -> QueryBuilder table -> QueryBuilder table
addCondition :: forall (table :: Symbol).
Condition -> QueryBuilder table -> QueryBuilder table
addCondition Condition
condition (QueryBuilder SQLQuery
sq) = SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder (SQLQuery -> QueryBuilder table) -> SQLQuery -> QueryBuilder table
forall a b. (a -> b) -> a -> b
$ SQLQuery
sq
    { whereCondition = case whereCondition sq of
        Maybe Condition
Nothing -> Condition -> Maybe Condition
forall a. a -> Maybe a
Just Condition
condition
        Just Condition
existing -> Condition -> Maybe Condition
forall a. a -> Maybe a
Just (Condition -> Condition -> Condition
AndCondition Condition
existing Condition
condition)
    }
{-# INLINE addCondition #-}

-- | A condition value: either a parameterized encoder or a literal SQL fragment.
data ConditionValue
    = Param !(Encoders.Params ())   -- ^ Parameterized value: compiler assigns $N
    | Literal !Text                 -- ^ Raw SQL text (for filterWhereSql, NULL comparisons, etc.)

instance Show ConditionValue where
    showsPrec :: Int -> ConditionValue -> ShowS
showsPrec Int
_ (Param Params ()
_) = String -> ShowS
Prelude.showString String
"<Param>"
    showsPrec Int
_ (Literal Text
t) = String -> ShowS
Prelude.showString String
"Literal " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Text -> ShowS
forall a. Show a => a -> ShowS
Prelude.shows Text
t

instance Eq ConditionValue where
    Literal Text
a == :: ConditionValue -> ConditionValue -> Bool
== Literal Text
b = Text
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
b
    ConditionValue
_ == ConditionValue
_ = Bool
False -- Params cannot be compared for equality

-- | Represents a WHERE condition
data Condition
    = ColumnCondition !Text !FilterOperator !ConditionValue !(Maybe Text) !(Maybe Text)
    --                ^col  ^op             ^value           ^applyLeft    ^applyRight
    | OrCondition !Condition !Condition
    | AndCondition !Condition !Condition

-- | Returns a numeric tag for each Condition constructor.
-- Pattern match is exhaustive so adding a constructor triggers -Wincomplete-patterns.
conditionTag :: Condition -> Int
conditionTag :: Condition -> Int
conditionTag ColumnCondition {} = Int
0
conditionTag OrCondition {} = Int
1
conditionTag AndCondition {} = Int
2

instance Eq Condition where
    (ColumnCondition Text
c1 FilterOperator
o1 ConditionValue
s1 Maybe Text
al1 Maybe Text
ar1) == :: Condition -> Condition -> Bool
== (ColumnCondition Text
c2 FilterOperator
o2 ConditionValue
s2 Maybe Text
al2 Maybe Text
ar2) = Text
c1 Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
c2 Bool -> Bool -> Bool
&& FilterOperator
o1 FilterOperator -> FilterOperator -> Bool
forall a. Eq a => a -> a -> Bool
== FilterOperator
o2 Bool -> Bool -> Bool
&& ConditionValue
s1 ConditionValue -> ConditionValue -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionValue
s2 Bool -> Bool -> Bool
&& Maybe Text
al1 Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Maybe Text
al2 Bool -> Bool -> Bool
&& Maybe Text
ar1 Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Maybe Text
ar2
    (OrCondition Condition
l1 Condition
r1) == (OrCondition Condition
l2 Condition
r2) = Condition
l1 Condition -> Condition -> Bool
forall a. Eq a => a -> a -> Bool
== Condition
l2 Bool -> Bool -> Bool
&& Condition
r1 Condition -> Condition -> Bool
forall a. Eq a => a -> a -> Bool
== Condition
r2
    (AndCondition Condition
l1 Condition
r1) == (AndCondition Condition
l2 Condition
r2) = Condition
l1 Condition -> Condition -> Bool
forall a. Eq a => a -> a -> Bool
== Condition
l2 Bool -> Bool -> Bool
&& Condition
r1 Condition -> Condition -> Bool
forall a. Eq a => a -> a -> Bool
== Condition
r2
    Condition
a == Condition
b = Condition -> Int
conditionTag Condition
a Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Condition -> Int
conditionTag Condition
b

deriving instance Show Condition

instance Show (QueryBuilder table) where
    show :: QueryBuilder table -> String
show (QueryBuilder SQLQuery
sq) = String
"QueryBuilder " String -> ShowS
forall {a}. Semigroup a => a -> a -> a
++ SQLQuery -> String
forall a. Show a => a -> String
Prelude.show SQLQuery
sq

instance Eq (QueryBuilder table) where
    (QueryBuilder SQLQuery
sq1) == :: QueryBuilder table -> QueryBuilder table -> Bool
== (QueryBuilder SQLQuery
sq2) = SQLQuery -> SQLQuery -> Bool
sqlQueryEq SQLQuery
sq1 SQLQuery
sq2

-- | Compare two SQLQuery values. Uses snippetEq for comparing Condition fields.
sqlQueryEq :: SQLQuery -> SQLQuery -> Bool
sqlQueryEq :: SQLQuery -> SQLQuery -> Bool
sqlQueryEq SQLQuery
a SQLQuery
b =
    SQLQuery -> Text
selectFrom SQLQuery
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Text
selectFrom SQLQuery
b
    Bool -> Bool -> Bool
&& SQLQuery -> Text
columnsSql SQLQuery
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Text
columnsSql SQLQuery
b
    Bool -> Bool -> Bool
&& SQLQuery -> Bool
distinctClause SQLQuery
a Bool -> Bool -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Bool
distinctClause SQLQuery
b
    Bool -> Bool -> Bool
&& SQLQuery -> Maybe Text
distinctOnClause SQLQuery
a Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Maybe Text
distinctOnClause SQLQuery
b
    Bool -> Bool -> Bool
&& Maybe Condition -> Maybe Condition -> Bool
forall {a}. Eq a => Maybe a -> Maybe a -> Bool
condEq (SQLQuery -> Maybe Condition
whereCondition SQLQuery
a) (SQLQuery -> Maybe Condition
whereCondition SQLQuery
b)
    Bool -> Bool -> Bool
&& SQLQuery -> [OrderByClause]
orderByClause SQLQuery
a [OrderByClause] -> [OrderByClause] -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> [OrderByClause]
orderByClause SQLQuery
b
    Bool -> Bool -> Bool
&& SQLQuery -> Maybe Int
limitClause SQLQuery
a Maybe Int -> Maybe Int -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Maybe Int
limitClause SQLQuery
b
    Bool -> Bool -> Bool
&& SQLQuery -> Maybe Int
offsetClause SQLQuery
a Maybe Int -> Maybe Int -> Bool
forall a. Eq a => a -> a -> Bool
== SQLQuery -> Maybe Int
offsetClause SQLQuery
b
  where
    condEq :: Maybe a -> Maybe a -> Bool
condEq Maybe a
Nothing Maybe a
Nothing = Bool
True
    condEq (Just a
c1) (Just a
c2) = a
c1 a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c2
    condEq Maybe a
_ Maybe a
_ = Bool
False

-- | Display QueryBuilder's as their sql query inside HSX
instance KnownSymbol table => ToHtml (QueryBuilder table) where
    toHtml :: QueryBuilder table -> Markup
toHtml QueryBuilder table
queryBuilder = Text -> Markup
forall a. ToHtml a => a -> Markup
toHtml (QueryBuilder table -> Text
toSQLQueryBuilder QueryBuilder table
queryBuilder)
      where
        -- Inline SQL generation for ToHtml to avoid circular imports
        toSQLQueryBuilder :: QueryBuilder table -> Text
        toSQLQueryBuilder :: QueryBuilder table -> Text
toSQLQueryBuilder QueryBuilder table
qb = Text
"QueryBuilder<" Text -> Text -> Text
forall {a}. Semigroup a => a -> a -> a
<> forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @table Text -> Text -> Text
forall {a}. Semigroup a => a -> a -> a
<> Text
">"

-- | ORDER BY direction
data OrderByDirection = Asc | Desc deriving (OrderByDirection -> OrderByDirection -> Bool
(OrderByDirection -> OrderByDirection -> Bool)
-> (OrderByDirection -> OrderByDirection -> Bool)
-> Eq OrderByDirection
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: OrderByDirection -> OrderByDirection -> Bool
== :: OrderByDirection -> OrderByDirection -> Bool
$c/= :: OrderByDirection -> OrderByDirection -> Bool
/= :: OrderByDirection -> OrderByDirection -> Bool
Eq, Int -> OrderByDirection -> ShowS
[OrderByDirection] -> ShowS
OrderByDirection -> String
(Int -> OrderByDirection -> ShowS)
-> (OrderByDirection -> String)
-> ([OrderByDirection] -> ShowS)
-> Show OrderByDirection
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> OrderByDirection -> ShowS
showsPrec :: Int -> OrderByDirection -> ShowS
$cshow :: OrderByDirection -> String
show :: OrderByDirection -> String
$cshowList :: [OrderByDirection] -> ShowS
showList :: [OrderByDirection] -> ShowS
Show, (forall x. OrderByDirection -> Rep OrderByDirection x)
-> (forall x. Rep OrderByDirection x -> OrderByDirection)
-> Generic OrderByDirection
forall x. Rep OrderByDirection x -> OrderByDirection
forall x. OrderByDirection -> Rep OrderByDirection x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. OrderByDirection -> Rep OrderByDirection x
from :: forall x. OrderByDirection -> Rep OrderByDirection x
$cto :: forall x. Rep OrderByDirection x -> OrderByDirection
to :: forall x. Rep OrderByDirection x -> OrderByDirection
GHC.Generics.Generic, OrderByDirection -> ()
(OrderByDirection -> ()) -> NFData OrderByDirection
forall a. (a -> ()) -> NFData a
$crnf :: OrderByDirection -> ()
rnf :: OrderByDirection -> ()
DeepSeq.NFData)

-- | Represents a complete SQL query after building
data SQLQuery = SQLQuery
    { SQLQuery -> Text
selectFrom :: !Text
    , SQLQuery -> Bool
distinctClause :: !Bool
    , SQLQuery -> Maybe Text
distinctOnClause :: !(Maybe Text)
    , SQLQuery -> Maybe Condition
whereCondition :: !(Maybe Condition)
    , SQLQuery -> [OrderByClause]
orderByClause :: ![OrderByClause]
    , SQLQuery -> Maybe Int
limitClause :: !(Maybe Int)
    , SQLQuery -> Maybe Int
offsetClause :: !(Maybe Int)
    , SQLQuery -> [Text]
columns :: ![Text]
    , SQLQuery -> Text
columnsSql :: !Text
    -- ^ Pre-computed qualified column selector, e.g. @"users.id, users.name"@.
    -- Built once at query construction time so the compiler avoids re-qualifying
    -- and re-joining the column list on every compilation.
    } deriving (Int -> SQLQuery -> ShowS
[SQLQuery] -> ShowS
SQLQuery -> String
(Int -> SQLQuery -> ShowS)
-> (SQLQuery -> String) -> ([SQLQuery] -> ShowS) -> Show SQLQuery
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SQLQuery -> ShowS
showsPrec :: Int -> SQLQuery -> ShowS
$cshow :: SQLQuery -> String
show :: SQLQuery -> String
$cshowList :: [SQLQuery] -> ShowS
showList :: [SQLQuery] -> ShowS
Show)


instance SetField "selectFrom" SQLQuery Text where setField :: Text -> SQLQuery -> SQLQuery
setField Text
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { selectFrom = value }
instance SetField "distinctClause" SQLQuery Bool where setField :: Bool -> SQLQuery -> SQLQuery
setField Bool
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { distinctClause = value }
instance SetField "distinctOnClause" SQLQuery (Maybe Text) where setField :: Maybe Text -> SQLQuery -> SQLQuery
setField Maybe Text
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { distinctOnClause = value }
instance SetField "whereCondition" SQLQuery (Maybe Condition) where setField :: Maybe Condition -> SQLQuery -> SQLQuery
setField Maybe Condition
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { whereCondition = value }
instance SetField "orderByClause" SQLQuery [OrderByClause] where setField :: [OrderByClause] -> SQLQuery -> SQLQuery
setField [OrderByClause]
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { orderByClause = value }
instance SetField "limitClause" SQLQuery (Maybe Int) where setField :: Maybe Int -> SQLQuery -> SQLQuery
setField Maybe Int
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { limitClause = value }
instance SetField "offsetClause" SQLQuery (Maybe Int) where setField :: Maybe Int -> SQLQuery -> SQLQuery
setField Maybe Int
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { offsetClause = value }

-- | Type class for default scoping of queries
class DefaultScope table where
    defaultScope :: QueryBuilder table -> QueryBuilder table

instance {-# OVERLAPPABLE #-} DefaultScope table where
    {-# INLINE defaultScope #-}
    defaultScope :: QueryBuilder table -> QueryBuilder table
defaultScope QueryBuilder table
queryBuilder = QueryBuilder table
queryBuilder

instance Table (GetModelByTableName table) => Default (QueryBuilder table) where
    {-# INLINE def #-}
    def :: QueryBuilder table
def = let tn :: Text
tn = forall record. Table record => Text
tableName @(GetModelByTableName table)
              cols :: [Text]
cols = forall record. Table record => [Text]
columnNames @(GetModelByTableName table)
          in SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
        { selectFrom :: Text
selectFrom = Text
tn
        , columns :: [Text]
columns = [Text]
cols
        , columnsSql :: Text
columnsSql = Text -> [Text] -> Text
qualifyAndJoinColumns Text
tn [Text]
cols
        , distinctClause :: Bool
distinctClause = Bool
False
        , distinctOnClause :: Maybe Text
distinctOnClause = Maybe Text
forall a. Maybe a
Nothing
        , whereCondition :: Maybe Condition
whereCondition = Maybe Condition
forall a. Maybe a
Nothing
        , orderByClause :: [OrderByClause]
orderByClause = []
        , limitClause :: Maybe Int
limitClause = Maybe Int
forall a. Maybe a
Nothing
        , offsetClause :: Maybe Int
offsetClause = Maybe Int
forall a. Maybe a
Nothing
        }

-- | Pre-compute the qualified, comma-separated column selector.
-- E.g. @qualifyAndJoinColumns "users" ["id", "name"] = "users.id, users.name"@
--
-- Intentionally NOINLINE: call sites (query, def) are lifted to CAFs, so
-- this is evaluated once per table type. NOINLINE prevents GHC from inlining
-- the map/intercalate into every use site of the resulting SQLQuery.
qualifyAndJoinColumns :: Text -> [Text] -> Text
qualifyAndJoinColumns :: Text -> [Text] -> Text
qualifyAndJoinColumns Text
tableName [Text]
columns =
    Text -> [Text] -> Text
Text.intercalate Text
", " ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
c -> Text
tableName Text -> Text -> Text
forall {a}. Semigroup a => a -> a -> a
<> Text
"." Text -> Text -> Text
forall {a}. Semigroup a => a -> a -> a
<> Text
c) [Text]
columns)
{-# NOINLINE qualifyAndJoinColumns #-}

-- | Helper to deal with @some_field IS NULL@ and @some_field = 'some value'@
class EqOrIsOperator value where toEqOrIsOperator :: value -> FilterOperator
instance {-# OVERLAPS #-} EqOrIsOperator (Maybe something) where toEqOrIsOperator :: Maybe something -> FilterOperator
toEqOrIsOperator Maybe something
Nothing = FilterOperator
IsOp; toEqOrIsOperator (Just something
_) = FilterOperator
EqOp
instance {-# OVERLAPPABLE #-} EqOrIsOperator otherwise where toEqOrIsOperator :: otherwise -> FilterOperator
toEqOrIsOperator otherwise
_ = FilterOperator
EqOp

-- | Type class for filtering by primary key
class FilterPrimaryKey table where
    filterWhereId :: Id' table -> QueryBuilder table -> QueryBuilder table