{-# LANGUAGE BangPatterns, TypeFamilies, DataKinds, PolyKinds, TypeApplications, ScopedTypeVariables, ConstraintKinds, TypeOperators, GADTs, UndecidableInstances, StandaloneDeriving, FunctionalDependencies, FlexibleContexts, InstanceSigs, AllowAmbiguousTypes, DeriveAnyClass #-}
{-|
Module: IHP.QueryBuilder.Compiler
Description: SQL compilation for QueryBuilder
Copyright: (c) digitally induced GmbH, 2020

This module provides functions to compile a QueryBuilder into SQL.
-}
module IHP.QueryBuilder.Compiler
( query
, buildQuery
, negateFilterOperator
, qualifiedColumnName
) where

import IHP.Prelude
import IHP.ModelSupport
import IHP.QueryBuilder.Types


-- | Returns the "NOT" version of an operator
--
-- >>> negateFilterOperator EqOp
-- NotEqOp
--
negateFilterOperator :: FilterOperator -> FilterOperator
negateFilterOperator :: FilterOperator -> FilterOperator
negateFilterOperator FilterOperator
EqOp = FilterOperator
NotEqOp
negateFilterOperator FilterOperator
NotEqOp = FilterOperator
EqOp
negateFilterOperator FilterOperator
InOp = FilterOperator
NotInOp
negateFilterOperator FilterOperator
NotInOp = FilterOperator
InOp
negateFilterOperator FilterOperator
IsOp = FilterOperator
IsNotOp
negateFilterOperator FilterOperator
IsNotOp = FilterOperator
IsOp
negateFilterOperator (LikeOp MatchSensitivity
matchSensitivity) = (MatchSensitivity -> FilterOperator
NotLikeOp MatchSensitivity
matchSensitivity)
negateFilterOperator (NotLikeOp MatchSensitivity
matchSensitivity) = (MatchSensitivity -> FilterOperator
LikeOp MatchSensitivity
matchSensitivity)
negateFilterOperator (MatchesOp MatchSensitivity
matchSensitivity) = Text -> FilterOperator
forall a. Text -> a
error Text
"not supported"
negateFilterOperator FilterOperator
GreaterThanOp = FilterOperator
LessThanOrEqualToOp
negateFilterOperator FilterOperator
GreaterThanOrEqualToOp = FilterOperator
LessThanOp
negateFilterOperator FilterOperator
LessThanOp = FilterOperator
GreaterThanOrEqualToOp
negateFilterOperator FilterOperator
LessThanOrEqualToOp = FilterOperator
GreaterThanOp
negateFilterOperator FilterOperator
SqlOp = FilterOperator
SqlOp

-- | Represent's a @SELECT * FROM ..@ query. It's the starting point to build a query.
-- Used together with the other functions to compose a sql query.
--
-- Example: Fetching all users
--
-- > allUsers <- query @User |> fetch
-- > -- Runs a 'SELECT * FROM users' query
--
-- You can use it together with 'filterWhere':
--
-- > activeUsers :: [User] <-
-- >    query @User
-- >     |> filterWhere (#active, True)
-- >     |> fetch
query :: forall model table. (table ~ GetTableName model, Table model) => DefaultScope table => QueryBuilder table
query :: forall model (table :: Symbol).
(table ~ GetTableName model, Table model, DefaultScope table) =>
QueryBuilder table
query = let tn :: Text
tn = forall record. Table record => Text
tableName @model
            cols :: [Text]
cols = forall record. Table record => [Text]
columnNames @model
        in (forall (table :: Symbol).
DefaultScope table =>
QueryBuilder table -> QueryBuilder table
defaultScope @table) QueryBuilder { unQueryBuilder :: SQLQuery
unQueryBuilder = 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
    } }
{-# INLINE query #-}

-- | Extract the SQLQuery from a QueryBuilder.
{-# INLINE buildQuery #-}
buildQuery :: forall table. KnownSymbol table => QueryBuilder table -> SQLQuery
buildQuery :: forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery (QueryBuilder SQLQuery
sq) = SQLQuery
sq

-- | Build a qualified column name like @tablename.column_name@ from a table name
-- and a camelCase field name. The field name is converted to snake_case via
-- 'fieldNameToColumnName'.
--
-- This is intentionally NOINLINE: the call sites in filterWhere, orderBy, etc.
-- are always lifted to CAFs (evaluated once at program start), so inlining
-- only duplicates the Text.Inflections parsing logic and Text concatenation
-- without any runtime benefit.
qualifiedColumnName :: Text -> Text -> Text
qualifiedColumnName :: Text -> Text -> Text
qualifiedColumnName Text
tableName Text
fieldName = Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
fieldNameToColumnName Text
fieldName
{-# NOINLINE qualifiedColumnName #-}