{-# 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 (..)
, Join (..)
, OrderByClause (..)
, OrderByDirection (..)
, FilterOperator (..)
, MatchSensitivity (..)
  -- * Type-level Join Tracking
, NoJoins
, EmptyModelList
, ConsModelList
, ModelList
, IsJoined
  -- * QueryBuilder Wrappers
, JoinQueryBuilderWrapper (..)
, NoJoinQueryBuilderWrapper (..)
, LabeledQueryBuilderWrapper (..)
  -- * Type Classes
, HasQueryBuilder (..)
, DefaultScope (..)
, EqOrIsOperator (..)
, FilterPrimaryKey (..)
) where

import IHP.Prelude
import IHP.ModelSupport
import IHP.HSX.ToHtml
import qualified Control.DeepSeq as DeepSeq
import qualified GHC.Generics
import qualified Hasql.DynamicStatements.Snippet as Snippet
import Hasql.DynamicStatements.Snippet (Snippet)
import qualified Prelude

-- | 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)

-- | Type-level marker indicating no joins are allowed
data NoJoins

-- | Type-level empty list for tracking joined tables
data EmptyModelList

-- | Type-level cons cell for tracking joined tables
data ConsModelList model models

-- | Type class to represent the true list type EmptyModelList ConsModelList.
class ModelList a

instance ModelList EmptyModelList
instance ModelList (ConsModelList model models)

-- | Type class to query containment in the type-level list.
class IsJoined a b

instance (ModelList b) => IsJoined a (ConsModelList a b)
instance {-# OVERLAPPABLE #-} (ModelList b, IsJoined a b) => IsJoined a (ConsModelList c b)

-- | Class to generalise over different QueryBuilder-providing types. The actual query builder can be extracted with 'getQueryBuilder' and injected with 'injectQueryBuilder'. Also assigns a join register to a queryBuilderProvider.
class HasQueryBuilder queryBuilderProvider joinRegister | queryBuilderProvider -> joinRegister where
    getQueryBuilder :: queryBuilderProvider table -> QueryBuilder table
    injectQueryBuilder :: QueryBuilder table -> queryBuilderProvider table
    getQueryIndex :: queryBuilderProvider table -> Maybe Text
    getQueryIndex queryBuilderProvider table
_ = Maybe Text
forall a. Maybe a
Nothing
    {-# INLINABLE getQueryIndex #-}

-- | Wrapper for QueryBuilders resulting from joins. Associates a joinRegister type.
newtype JoinQueryBuilderWrapper joinRegister table = JoinQueryBuilderWrapper (QueryBuilder table)

-- | Wrapper for QueryBuilder that must not joins, e.g. queryUnion.
newtype NoJoinQueryBuilderWrapper table = NoJoinQueryBuilderWrapper (QueryBuilder table)

-- | Wrapper for QueryBuilders with indexed results.
newtype LabeledQueryBuilderWrapper foreignTable indexColumn indexValue table = LabeledQueryBuilderWrapper (QueryBuilder table)

-- | QueryBuilders have query builders and the join register is empty.
instance HasQueryBuilder QueryBuilder EmptyModelList where
    getQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
getQueryBuilder = QueryBuilder table -> QueryBuilder table
forall a. a -> a
forall {k} (cat :: k -> k -> *) (a :: k). Category cat => cat a a
id
    {-# INLINE getQueryBuilder #-}
    injectQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
injectQueryBuilder = QueryBuilder table -> QueryBuilder table
forall a. a -> a
forall {k} (cat :: k -> k -> *) (a :: k). Category cat => cat a a
id
    {-# INLINE injectQueryBuilder #-}

-- | JoinQueryBuilderWrappers have query builders
instance HasQueryBuilder (JoinQueryBuilderWrapper joinRegister) joinRegister where
    getQueryBuilder :: forall (table :: Symbol).
JoinQueryBuilderWrapper joinRegister table -> QueryBuilder table
getQueryBuilder (JoinQueryBuilderWrapper QueryBuilder table
queryBuilder) = QueryBuilder table
queryBuilder
    {-# INLINABLE getQueryBuilder #-}
    injectQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> JoinQueryBuilderWrapper joinRegister table
injectQueryBuilder = QueryBuilder table -> JoinQueryBuilderWrapper joinRegister table
forall k (joinRegister :: k) (table :: Symbol).
QueryBuilder table -> JoinQueryBuilderWrapper joinRegister table
JoinQueryBuilderWrapper
    {-# INLINABLE injectQueryBuilder #-}

-- | NoJoinQueryBuilderWrapper have query builders and the join register does not allow any joins
instance HasQueryBuilder NoJoinQueryBuilderWrapper NoJoins where
    getQueryBuilder :: forall (table :: Symbol).
NoJoinQueryBuilderWrapper table -> QueryBuilder table
getQueryBuilder (NoJoinQueryBuilderWrapper QueryBuilder table
queryBuilder) = QueryBuilder table
queryBuilder
    {-# INLINABLE getQueryBuilder #-}
    injectQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> NoJoinQueryBuilderWrapper table
injectQueryBuilder  = QueryBuilder table -> NoJoinQueryBuilderWrapper table
forall (table :: Symbol).
QueryBuilder table -> NoJoinQueryBuilderWrapper table
NoJoinQueryBuilderWrapper
    {-# INLINABLE injectQueryBuilder #-}

instance (KnownSymbol foreignTable, foreignModel ~ GetModelByTableName foreignTable , KnownSymbol indexColumn, HasField indexColumn foreignModel indexValue) => HasQueryBuilder (LabeledQueryBuilderWrapper foreignTable indexColumn indexValue) NoJoins where
    getQueryBuilder :: forall (table :: Symbol).
LabeledQueryBuilderWrapper
  foreignTable indexColumn indexValue table
-> QueryBuilder table
getQueryBuilder (LabeledQueryBuilderWrapper QueryBuilder table
queryBuilder) = QueryBuilder table
queryBuilder
    {-# INLINABLE getQueryBuilder #-}
    injectQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> LabeledQueryBuilderWrapper
     foreignTable indexColumn indexValue table
injectQueryBuilder = QueryBuilder table
-> LabeledQueryBuilderWrapper
     foreignTable indexColumn indexValue table
forall {k} {k} {k} (foreignTable :: k) (indexColumn :: k)
       (indexValue :: k) (table :: Symbol).
QueryBuilder table
-> LabeledQueryBuilderWrapper
     foreignTable indexColumn indexValue table
LabeledQueryBuilderWrapper
    {-# INLINABLE injectQueryBuilder #-}
    getQueryIndex :: forall (table :: Symbol).
LabeledQueryBuilderWrapper
  foreignTable indexColumn indexValue table
-> Maybe Text
getQueryIndex LabeledQueryBuilderWrapper
  foreignTable indexColumn indexValue table
_ = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @foreignTable Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
fieldNameToColumnName (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @indexColumn)
    {-# INLINABLE getQueryIndex #-}

-- | The main QueryBuilder data type, representing different query operations
data QueryBuilder (table :: Symbol) =
    NewQueryBuilder { forall (table :: Symbol). QueryBuilder table -> Text
selectFrom :: !Text, forall (table :: Symbol). QueryBuilder table -> [Text]
columns :: ![Text] }
    | DistinctQueryBuilder   { forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder :: !(QueryBuilder table) }
    | DistinctOnQueryBuilder { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> Text
distinctOnColumn :: !Text }
    | FilterByQueryBuilder   { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol).
QueryBuilder table -> (Text, FilterOperator, Snippet)
queryFilter :: !(Text, FilterOperator, Snippet), forall (table :: Symbol). QueryBuilder table -> Maybe Text
applyLeft :: !(Maybe Text), forall (table :: Symbol). QueryBuilder table -> Maybe Text
applyRight :: !(Maybe Text) }
    | OrderByQueryBuilder    { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> OrderByClause
queryOrderByClause :: !OrderByClause }
    | LimitQueryBuilder      { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> Int
queryLimit :: !Int }
    | OffsetQueryBuilder     { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> Int
queryOffset :: !Int }
    | UnionQueryBuilder      { forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
firstQueryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
secondQueryBuilder :: !(QueryBuilder table) }
    | JoinQueryBuilder       { queryBuilder :: !(QueryBuilder table), forall (table :: Symbol). QueryBuilder table -> Join
joinData :: Join}

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

-- | Snippet doesn't have a Show instance, so we provide one for debugging QueryBuilder
instance Show Snippet where
    showsPrec :: Int -> Snippet -> ShowS
showsPrec Int
_ Snippet
_ = String -> ShowS
Prelude.showString String
"<Snippet>"

-- | Snippet is an opaque type with no Eq instance. We compare snippets by their
-- rendered SQL template via 'Snippet.toSql'. This only compares the SQL structure
-- (e.g. @col = $1@), not the parameter values.
snippetEq :: Snippet -> Snippet -> Bool
snippetEq :: Snippet -> Snippet -> Bool
snippetEq Snippet
a Snippet
b = Snippet -> Text
Snippet.toSql Snippet
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Snippet -> Text
Snippet.toSql Snippet
b

-- | 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 Snippet
s1 Maybe Text
al1 Maybe Text
ar1) == :: Condition -> Condition -> Bool
== (ColumnCondition Text
c2 FilterOperator
o2 Snippet
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
&& Snippet -> Snippet -> Bool
snippetEq Snippet
s1 Snippet
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

-- | Returns a numeric tag for each QueryBuilder constructor.
-- Pattern match is exhaustive so adding a constructor triggers -Wincomplete-patterns.
queryBuilderTag :: QueryBuilder table -> Int
queryBuilderTag :: forall (table :: Symbol). QueryBuilder table -> Int
queryBuilderTag NewQueryBuilder {} = Int
0
queryBuilderTag DistinctQueryBuilder {} = Int
1
queryBuilderTag DistinctOnQueryBuilder {} = Int
2
queryBuilderTag FilterByQueryBuilder {} = Int
3
queryBuilderTag OrderByQueryBuilder {} = Int
4
queryBuilderTag LimitQueryBuilder {} = Int
5
queryBuilderTag OffsetQueryBuilder {} = Int
6
queryBuilderTag UnionQueryBuilder {} = Int
7
queryBuilderTag JoinQueryBuilder {} = Int
8

instance Eq (QueryBuilder table) where
    (NewQueryBuilder Text
s1 [Text]
c1) == :: QueryBuilder table -> QueryBuilder table -> Bool
== (NewQueryBuilder Text
s2 [Text]
c2) = Text
s1 Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
s2 Bool -> Bool -> Bool
&& [Text]
c1 [Text] -> [Text] -> Bool
forall a. Eq a => a -> a -> Bool
== [Text]
c2
    (DistinctQueryBuilder QueryBuilder table
q1) == (DistinctQueryBuilder QueryBuilder table
q2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2
    (DistinctOnQueryBuilder QueryBuilder table
q1 Text
d1) == (DistinctOnQueryBuilder QueryBuilder table
q2 Text
d2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& Text
d1 Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
d2
    (FilterByQueryBuilder QueryBuilder table
q1 (Text
b1, FilterOperator
op1, Snippet
sn1) Maybe Text
l1 Maybe Text
r1) == (FilterByQueryBuilder QueryBuilder table
q2 (Text
b2, FilterOperator
op2, Snippet
sn2) Maybe Text
l2 Maybe Text
r2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& Text
b1 Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
b2 Bool -> Bool -> Bool
&& FilterOperator
op1 FilterOperator -> FilterOperator -> Bool
forall a. Eq a => a -> a -> Bool
== FilterOperator
op2 Bool -> Bool -> Bool
&& Snippet -> Snippet -> Bool
snippetEq Snippet
sn1 Snippet
sn2 Bool -> Bool -> Bool
&& Maybe Text
l1 Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Maybe Text
l2 Bool -> Bool -> Bool
&& Maybe Text
r1 Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Maybe Text
r2
    (OrderByQueryBuilder QueryBuilder table
q1 OrderByClause
o1) == (OrderByQueryBuilder QueryBuilder table
q2 OrderByClause
o2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& OrderByClause
o1 OrderByClause -> OrderByClause -> Bool
forall a. Eq a => a -> a -> Bool
== OrderByClause
o2
    (LimitQueryBuilder QueryBuilder table
q1 Int
l1) == (LimitQueryBuilder QueryBuilder table
q2 Int
l2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& Int
l1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
l2
    (OffsetQueryBuilder QueryBuilder table
q1 Int
o1) == (OffsetQueryBuilder QueryBuilder table
q2 Int
o2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& Int
o1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
o2
    (UnionQueryBuilder QueryBuilder table
f1 QueryBuilder table
s1) == (UnionQueryBuilder QueryBuilder table
f2 QueryBuilder table
s2) = QueryBuilder table
f1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
f2 Bool -> Bool -> Bool
&& QueryBuilder table
s1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
s2
    (JoinQueryBuilder QueryBuilder table
q1 Join
j1) == (JoinQueryBuilder QueryBuilder table
q2 Join
j2) = QueryBuilder table
q1 QueryBuilder table -> QueryBuilder table -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table
q2 Bool -> Bool -> Bool
&& Join
j1 Join -> Join -> Bool
forall a. Eq a => a -> a -> Bool
== Join
j2
    QueryBuilder table
a == QueryBuilder table
b = QueryBuilder table -> Int
forall (table :: Symbol). QueryBuilder table -> Int
queryBuilderTag QueryBuilder table
a Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== QueryBuilder table -> Int
forall (table :: Symbol). QueryBuilder table -> Int
queryBuilderTag QueryBuilder table
b

deriving instance Show Condition
deriving instance Show (QueryBuilder table)

-- | Display QueryBuilder's as their sql query inside HSX
instance KnownSymbol table => ToHtml (QueryBuilder table) where
    toHtml :: QueryBuilder table -> Html
toHtml QueryBuilder table
queryBuilder = Text -> Html
forall a. ToHtml a => a -> Html
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
">"

-- | Represents a JOIN clause
data Join = Join { Join -> Text
table :: Text, Join -> Text
tableJoinColumn :: Text, Join -> Text
otherJoinColumn :: Text }
    deriving (Int -> Join -> ShowS
[Join] -> ShowS
Join -> String
(Int -> Join -> ShowS)
-> (Join -> String) -> ([Join] -> ShowS) -> Show Join
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Join -> ShowS
showsPrec :: Int -> Join -> ShowS
$cshow :: Join -> String
show :: Join -> String
$cshowList :: [Join] -> ShowS
showList :: [Join] -> ShowS
Show, Join -> Join -> Bool
(Join -> Join -> Bool) -> (Join -> Join -> Bool) -> Eq Join
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Join -> Join -> Bool
== :: Join -> Join -> Bool
$c/= :: Join -> Join -> Bool
/= :: Join -> Join -> Bool
Eq)

-- | 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 -> Maybe Text
queryIndex :: !(Maybe Text)
    , SQLQuery -> Text
selectFrom :: !Text
    , SQLQuery -> Bool
distinctClause :: !Bool
    , SQLQuery -> Maybe Text
distinctOnClause :: !(Maybe Text)
    , SQLQuery -> Maybe Condition
whereCondition :: !(Maybe Condition)
    , SQLQuery -> [Join]
joins :: ![Join]
    , SQLQuery -> [OrderByClause]
orderByClause :: ![OrderByClause]
    , SQLQuery -> Maybe Int
limitClause :: !(Maybe Int)
    , SQLQuery -> Maybe Int
offsetClause :: !(Maybe Int)
    , SQLQuery -> [Text]
columns :: ![Text]
    } 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 "queryIndex" SQLQuery (Maybe Text) where setField :: Maybe Text -> SQLQuery -> SQLQuery
setField Maybe Text
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { queryIndex = value }
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 = NewQueryBuilder { selectFrom :: Text
selectFrom = forall record. Table record => Text
tableName @(GetModelByTableName table), columns :: [Text]
columns = forall record. Table record => [Text]
columnNames @(GetModelByTableName table) }

-- | 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