Abstract out select query construction

This commit is contained in:
Correl Roush 2022-03-27 17:52:44 -04:00
parent 4df535f3c5
commit f6936baae7
3 changed files with 157 additions and 59 deletions

View file

@ -45,6 +45,7 @@ import Network.Wai.Logger
import qualified Paths_tutor as PT
import Search
import SearchParser
import Select
import Servant
( Description,
Get,
@ -315,64 +316,48 @@ status =
searchCards :: FilePath -> Maybe T.Text -> Maybe T.Text -> Maybe T.Text -> Handler [Card]
searchCards dbFile q sortBy inCollection =
liftIO results
where
search = parseQuery $ fromMaybe "" q
defaultOrderings :: [Query]
defaultOrderings =
[ "rarities.rarity_ord DESC",
"length(cards.color_identity) DESC",
[sql|CASE WHEN length(cards.color_identity) > 0 THEN '0'
ELSE cards.color_identity END ASC|],
"cards.name ASC"
]
orderSql :: Query
orderSql =
"ORDER BY " <> case sortBy of
Just "price" -> sqlJoin "," $ "CAST(COALESCE(card_prices.usd, card_prices.usd_foil) as decimal) DESC" : defaultOrderings
_ -> sqlJoin "," defaultOrderings
joins :: [Query]
joins =
[ [sql|JOIN card_prices ON (cards.scryfall_id = card_prices.scryfall_id)
AND card_prices.date = (SELECT value FROM vars WHERE key = :last_update_key)|],
[sql|JOIN rarities ON (cards.rarity = rarities.rarity)|]
]
joinSql :: Query
joinSql = case inCollection of
Just "no" -> sqlJoin "" joins
_ -> sqlJoin "JOIN copies ON (cards.scryfall_id = copies.scryfall_id)" $ joins
results :: IO [Card]
results = withConnection dbFile $ \conn ->
liftIO $
withConnection dbFile $ \conn ->
queryNamed
conn
( sqlJoin
" "
[ [sql|
SELECT cards.scryfall_id,
cards.name,
cards.set_code,
cards.collector_number,
cards.rarity,
cards.color_identity,
cards.oracle_text,
card_prices.usd,
card_prices.usd_foil,
card_prices.eur,
card_prices.eur_foil,
card_prices.tix
FROM cards|],
joinSql,
orderSql,
"LIMIT 10"
]
)
[":last_update_key" := ("last_update" :: T.Text)]
sqlJoin :: Query -> [Query] -> Query
sqlJoin joiner snippets =
mconcat $ L.intercalate [joiner] [[s] | s <- snippets]
(toQuery query)
(selectParameters query)
where
baseQuery =
selectSimple
[ "cards.scryfall_id",
"cards.name",
"cards.set_code",
"cards.collector_number",
"cards.rarity",
"cards.color_identity",
"cards.oracle_text",
"card_prices.usd",
"card_prices.usd_foil",
"card_prices.eur",
"card_prices.eur_foil",
"card_prices.tix"
]
["cards"]
<> join
[sql|card_prices ON (cards.scryfall_id = card_prices.scryfall_id)
AND card_prices.date = (SELECT value FROM vars WHERE key = :last_update_key)|]
<> join "rarities ON (cards.rarity = rarities.rarity)"
<> orderBy "rarities.rarity_ord DESC"
<> orderBy "length(cards.color_identity) DESC"
<> orderBy
[sql|CASE WHEN length(cards.color_identity) > 0 THEN '0'
ELSE cards.color_identity END ASC|]
<> orderBy "cards.name ASC"
<> parameter ":last_update_key" ("last_update" :: T.Text)
<> limit 10
query =
mconcat $ catMaybes
[ case sortBy of
Just "price" -> Just $ orderBy "CAST(COALESCE(card_prices.usd, card_prices.usd_foil) as decimal) DESC"
_ -> Nothing,
Just baseQuery,
case inCollection of
Just "no" -> Nothing
_ -> Just $ join "copies ON (cards.scryfall_id = copies.scryfall_id)"
]

112
src/Select.hs Normal file
View file

@ -0,0 +1,112 @@
{-# LANGUAGE OverloadedStrings #-}
module Select
( toQuery,
selectSimple,
selectWhere,
selectParameters,
column,
table,
join,
where_,
orderBy,
limit,
limitOffset,
parameter,
)
where
import Control.Monad (unless)
import Data.List
import Data.Map
import Data.Maybe
import Data.String
import Data.Text
import Database.SQLite.Simple
import Database.SQLite.Simple.Ok
import Search
data Select a = Select
{ selectColumns :: [a],
selectTables :: [a],
selectJoins :: [a],
selectCriteria :: [a],
selectOrdering :: [a],
selectLimit :: Maybe Int,
selectOffset :: Maybe Int,
selectParameters :: [NamedParam]
}
instance Semigroup (Select a) where
(<>) a b =
Select
(selectColumns a <> selectColumns b)
(selectTables a <> selectTables b)
(selectJoins a <> selectJoins b)
(selectCriteria a <> selectCriteria b)
(selectOrdering a <> selectOrdering a)
(pickLast (selectLimit a) (selectLimit b))
(pickLast (selectOffset a) (selectOffset b))
(selectParameters a <> selectParameters b)
where
pickLast _ (Just b) = Just b
pickLast a Nothing = a
instance Monoid (Select a) where
mempty = selectSimple [] []
toQuery :: (Monoid a, Data.String.IsString a) => Select a -> a
toQuery select =
sqlJoin
" "
$ [ "SELECT",
sqlColumns,
"FROM",
sqlTables
]
<> catMaybes
[ sqlJoins,
sqlWhere,
sqlOrdering,
sqlLimitOffset
]
where
sqlColumns = sqlJoin ", " $ selectColumns select
sqlTables = sqlJoin ", " $ selectTables select
sqlJoins = case selectJoins select of
[] -> Nothing
xs -> Just $ sqlJoin " " $ ["JOIN " <> x | x <- xs]
sqlWhere = case selectCriteria select of
[] -> Nothing
xs -> Just $ "WHERE " <> sqlJoin " AND " xs
sqlOrdering = case selectOrdering select of
[] -> Nothing
xs -> Just $ "ORDER BY " <> sqlJoin ", " xs
sqlLimitOffset = case (selectLimit select, selectOffset select) of
(Just l, Just o) -> Just "LIMIT :__limit, :__offset"
(Just l, Nothing) -> Just "LIMIT :__limit"
_ -> Nothing
sqlJoin :: (Monoid a, Data.String.IsString a) => a -> [a] -> a
sqlJoin joiner snippets =
mconcat $ Data.List.intercalate [joiner] [[s] | s <- snippets]
selectSimple columns tables = Select columns tables [] [] [] Nothing Nothing []
selectWhere columns tables criteria = Select columns tables criteria [] [] Nothing Nothing []
column x = selectSimple [x] []
table x = selectSimple [] [x]
where_ x = selectWhere [] [] [x]
join x = Select [] [] [x] [] [] Nothing Nothing []
orderBy x = Select [] [] [] [] [x] Nothing Nothing []
limit x = Select [] [] [] [] [] (Just x) Nothing [":__limit" := x]
limitOffset x y = Select [] [] [] [] [] (Just x) (Just y) [":__limit" := x, ":__offset" := y]
parameter name value = Select [] [] [] [] [] Nothing Nothing [name := value]

View file

@ -26,6 +26,7 @@ library
Lib
Search
SearchParser
Select
other-modules:
Paths_tutor
hs-source-dirs: