module IHP.Pagination.ControllerFunctions
( paginate,
paginateWithOptions,
filterList,
defaultPaginationOptions,
paginatedSqlQuery,
paginatedSqlQueryWithOptions,
) where
import IHP.Prelude
import IHP.Controller.Context
import IHP.Controller.Param ( paramOrDefault, paramOrNothing )
import IHP.Pagination.Types ( Options(..), Pagination(..) )
import IHP.QueryBuilder ( QueryBuilder, filterWhereILike, limit, offset )
import IHP.Fetch (fetchCount)
import IHP.ModelSupport (GetModelByTableName, sqlQueryHasql, Table)
import IHP.Hasql.FromRow (FromRowHasql(..))
import IHP.Hasql.Encoders (ToSnippetParams(..), sqlToSnippet)
import Network.Wai (Request)
import qualified Hasql.Decoders as Decoders
import qualified Hasql.DynamicStatements.Snippet as Snippet
import Data.Text.Encoding (encodeUtf8)
paginate :: forall controller table .
(?context::ControllerContext
, ?modelContext :: ModelContext
, ?theAction :: controller
, ?request :: Request
, KnownSymbol table) =>
QueryBuilder table
-> IO (QueryBuilder table, Pagination)
paginate :: forall controller (table :: Symbol).
(?context::ControllerContext, ?modelContext::ModelContext,
?theAction::controller, ?request::Request, KnownSymbol table) =>
QueryBuilder table -> IO (QueryBuilder table, Pagination)
paginate = Options
-> QueryBuilder table -> IO (QueryBuilder table, Pagination)
forall controller (table :: Symbol).
(?context::ControllerContext, ?modelContext::ModelContext,
?theAction::controller, ?request::Request, KnownSymbol table) =>
Options
-> QueryBuilder table -> IO (QueryBuilder table, Pagination)
paginateWithOptions Options
defaultPaginationOptions
paginateWithOptions :: forall controller table .
(?context::ControllerContext
, ?modelContext :: ModelContext
, ?theAction :: controller
, ?request :: Request
, KnownSymbol table) =>
Options
-> QueryBuilder table
-> IO (QueryBuilder table, Pagination)
paginateWithOptions :: forall controller (table :: Symbol).
(?context::ControllerContext, ?modelContext::ModelContext,
?theAction::controller, ?request::Request, KnownSymbol table) =>
Options
-> QueryBuilder table -> IO (QueryBuilder table, Pagination)
paginateWithOptions Options
options QueryBuilder table
query = do
count <- QueryBuilder table
query
QueryBuilder table -> (QueryBuilder table -> IO Int) -> IO Int
forall a b. a -> (a -> b) -> b
|> QueryBuilder table -> IO Int
forall (table :: Symbol).
(?modelContext::ModelContext, KnownSymbol table) =>
QueryBuilder table -> IO Int
fetchCount
let pageSize = (?request::Request) => Options -> Int
Options -> Int
pageSize' Options
options
pagination = Pagination
{ currentPage :: Int
currentPage = Int
(?request::Request) => Int
page
, totalItems :: Int
totalItems = Int -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
count
, pageSize :: Int
pageSize = Int
pageSize
, window :: Int
window = Options -> Int
windowSize Options
options
}
let results = QueryBuilder table
query
QueryBuilder table
-> (QueryBuilder table -> QueryBuilder table) -> QueryBuilder table
forall a b. a -> (a -> b) -> b
|> Int -> QueryBuilder table -> QueryBuilder table
forall (model :: Symbol).
Int -> QueryBuilder model -> QueryBuilder model
limit Int
pageSize
QueryBuilder table
-> (QueryBuilder table -> QueryBuilder table) -> QueryBuilder table
forall a b. a -> (a -> b) -> b
|> Int -> QueryBuilder table -> QueryBuilder table
forall (model :: Symbol).
Int -> QueryBuilder model -> QueryBuilder model
offset (Int -> Int -> Int
offset' Int
pageSize Int
(?request::Request) => Int
page)
pure
( results
, pagination
)
filterList :: forall name table model .
(?context::ControllerContext
, ?request :: Request
, KnownSymbol name
, HasField name model Text
, model ~ GetModelByTableName table
, KnownSymbol table
, Table model
) =>
Proxy name
-> QueryBuilder table
-> QueryBuilder table
filterList :: forall (name :: Symbol) (table :: Symbol) model.
(?context::ControllerContext, ?request::Request, KnownSymbol name,
HasField name model Text, model ~ GetModelByTableName table,
KnownSymbol table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
filterList Proxy name
field =
case forall paramType.
(?request::Request, ParamReader (Maybe paramType)) =>
ByteString -> Maybe paramType
paramOrNothing @Text ByteString
"filter" of
Just Text
uf -> (Proxy name, Text) -> QueryBuilder table -> QueryBuilder table
forall (name :: Symbol) (table :: Symbol) model value.
(KnownSymbol table, KnownSymbol name, DefaultParamEncoder value,
HasField name model value, model ~ GetModelByTableName table,
Table model) =>
(Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereILike (Proxy name
field, Text
"%" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
uf Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"%")
Maybe Text
Nothing -> QueryBuilder table -> QueryBuilder table
forall a. a -> a
forall {k} (cat :: k -> k -> *) (a :: k). Category cat => cat a a
id
defaultPaginationOptions :: Options
=
Options
{ maxItems :: Int
maxItems = Int
50
, windowSize :: Int
windowSize = Int
5
}
paginatedSqlQuery
:: ( FromRowHasql model
, ToSnippetParams parameters
, ?context :: ControllerContext
, ?modelContext :: ModelContext
, ?request :: Request
)
=> Text -> parameters -> IO ([model], Pagination)
paginatedSqlQuery :: forall model parameters.
(FromRowHasql model, ToSnippetParams parameters,
?context::ControllerContext, ?modelContext::ModelContext,
?request::Request) =>
Text -> parameters -> IO ([model], Pagination)
paginatedSqlQuery = Options -> Text -> parameters -> IO ([model], Pagination)
forall model parameters.
(FromRowHasql model, ToSnippetParams parameters,
?context::ControllerContext, ?modelContext::ModelContext,
?request::Request) =>
Options -> Text -> parameters -> IO ([model], Pagination)
paginatedSqlQueryWithOptions Options
defaultPaginationOptions
paginatedSqlQueryWithOptions
:: ( FromRowHasql model
, ToSnippetParams parameters
, ?context :: ControllerContext
, ?modelContext :: ModelContext
, ?request :: Request
)
=> Options -> Text -> parameters -> IO ([model], Pagination)
paginatedSqlQueryWithOptions :: forall model parameters.
(FromRowHasql model, ToSnippetParams parameters,
?context::ControllerContext, ?modelContext::ModelContext,
?request::Request) =>
Options -> Text -> parameters -> IO ([model], Pagination)
paginatedSqlQueryWithOptions Options
options Text
sql parameters
placeholders = do
let pool :: Pool
pool = ?modelContext::ModelContext
ModelContext
?modelContext.hasqlPool
let baseParams :: [Snippet]
baseParams = parameters -> [Snippet]
forall a. ToSnippetParams a => a -> [Snippet]
toSnippetParams parameters
placeholders
let countSnippet :: Snippet
countSnippet = ByteString -> [Snippet] -> Snippet
sqlToSnippet (ByteString
"SELECT count(subquery.*) FROM (" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
encodeUtf8 Text
sql ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
") as subquery") [Snippet]
baseParams
count :: Int <- Pool -> Snippet -> Result Int -> IO Int
forall a.
(?modelContext::ModelContext) =>
Pool -> Snippet -> Result a -> IO a
sqlQueryHasql Pool
pool Snippet
countSnippet (Row Int -> Result Int
forall a. Row a -> Result a
Decoders.singleRow (NullableOrNot Value Int -> Row Int
forall a. NullableOrNot Value a -> Row a
Decoders.column (Value Int -> NullableOrNot Value Int
forall (decoder :: * -> *) a. decoder a -> NullableOrNot decoder a
Decoders.nonNullable (Int64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int64 -> Int) -> Value Int64 -> Value Int
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Value Int64
Decoders.int8))))
let pageSize = (?request::Request) => Options -> Int
Options -> Int
pageSize' Options
options
pagination = Pagination
{ pageSize :: Int
pageSize = Int
pageSize
, totalItems :: Int
totalItems = Int -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
count
, currentPage :: Int
currentPage = Int -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
(?request::Request) => Int
page
, window :: Int
window = Options -> Int
windowSize Options
options
}
let resultsSnippet = ByteString -> [Snippet] -> Snippet
sqlToSnippet (ByteString
"SELECT subquery.* FROM (" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
encodeUtf8 Text
sql ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
") as subquery LIMIT ? OFFSET ?") ([Snippet]
baseParams [Snippet] -> [Snippet] -> [Snippet]
forall a. Semigroup a => a -> a -> a
<> [Int -> Snippet
forall param. DefaultParamEncoder param => param -> Snippet
Snippet.param Int
pageSize, Int -> Snippet
forall param. DefaultParamEncoder param => param -> Snippet
Snippet.param (Int -> Int -> Int
offset' Int
pageSize Int
(?request::Request) => Int
page)])
results :: [model] <- sqlQueryHasql pool resultsSnippet (Decoders.rowList hasqlRowDecoder)
pure (results, pagination)
pageSize' :: (?request :: Request) => Options -> Int
pageSize' :: (?request::Request) => Options -> Int
pageSize' Options
options = Int -> Int -> Int
forall a. Ord a => a -> a -> a
min (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ forall a.
(?request::Request, ParamReader a) =>
a -> ByteString -> a
paramOrDefault @Int (Options -> Int
maxItems Options
options) ByteString
"maxItems") Int
200
page :: (?request :: Request) => Int
page :: (?request::Request) => Int
page = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ forall a.
(?request::Request, ParamReader a) =>
a -> ByteString -> a
paramOrDefault @Int Int
1 ByteString
"page"
offset' :: Int -> Int -> Int
offset' :: Int -> Int -> Int
offset' Int
pageSize Int
page = (Int
page Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
pageSize