preql-0.4: src/Preql/QuasiQuoter/Syntax/TH.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TemplateHaskell #-}
module Preql.QuasiQuoter.Syntax.TH where
import Preql.Imports
import Preql.QuasiQuoter.Common
import Preql.QuasiQuoter.Syntax.Params
import Preql.QuasiQuoter.Syntax.Parser (parseStatement, parseSelect)
import Preql.QuasiQuoter.Syntax.Printer (formatAsByteString)
import Preql.QuasiQuoter.Syntax.Syntax as Syntax hiding (select)
import Preql.Wire.Internal as Wire (Query(..))
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import qualified Data.Text as T
tupleType :: [Name] -> Type
tupleType [v] = VarT v
tupleType names = foldl (\expr v -> AppT expr (VarT v)) (TupleT n) names
where n = length names
-- | Synthesize a Query tagged with the number of returned columns.
makeArityQuery :: Statement -> Q Exp
makeArityQuery parsed = do
let
width = case countColumnsReturned parsed of
Just n -> pure (LitT (NumTyLit (fromIntegral n)))
Nothing -> VarT <$> newName "r" -- SELECT *
formatted = formatAsByteString parsed
[e| Wire.Query formatted :: Wire.Query $(width) |]
-- | This quasiquoter will accept most syntactically valid SELECT
-- queries. Language features not yet implemented include type casts,
-- lateral joins, EXTRACT, INTO, string & XML operators, and
-- user-defined operators. For now, please fall back to
-- 'Preql.QuasiQuoter.Raw.TH.sql' for these less-frequently used SQL
-- features, or file a bug report if a commonly used feature is not
-- parsed correctly.
--
-- @select@ accepts antiquotes with the same syntax as 'sql'.
select :: QuasiQuoter
select = expressionOnly "select" (aritySql parseSelect QS)
-- | This quasiquoter will accept all queries accepted by 'select',
-- and limited INSERT, UPDATE, and DELETE queries. For details of
-- what can be parsed, consult Parser.y
validSql :: QuasiQuoter
validSql = expressionOnly "validSql" (aritySql parseStatement id)
aritySql :: (String -> String -> Either String a) -> (a -> Statement) -> String -> Q Exp
aritySql parse mkStatement raw = do
loc <- location
let e_ast = mkStatement <$> parse (show loc) raw
case e_ast of
Right parsed -> do
let
positionalCount = maxParam parsed
(rewritten, aqs) = numberAntiquotes positionalCount parsed
antiNames = map (mkName . T.unpack) (haskellExpressions aqs)
typedQuery <- makeArityQuery rewritten
case positionalCount of
0 -> -- only antiquotes (or no params)
return $ tupleE [typedQuery, tupleOrSingle antiNames]
1 -> do -- one positional param, doesn't take a tuple
patternName <- newName "c"
return $ LamE [VarP patternName]
(tupleE [typedQuery, tupleOrSingle (patternName : antiNames)])
_ -> do -- at least two positional parameters
patternNames <- cNames 'q' (fromIntegral positionalCount)
return $ LamE
[TupP (map VarP patternNames)]
(tupleE [typedQuery, tupleOrSingle (patternNames ++ antiNames)])
Left err -> error err
countColumnsReturned :: Statement -> Maybe Int
countColumnsReturned (QS selectQ) = go selectQ where
go s = case s of
SelectValues rows -> Just (foldl' max 0 (fmap length rows))
Simple Select {targetList} -> if Star `elem` targetList
then Nothing
else Just (length targetList)
S ss _ -> go ss
Set _ _ a b -> case (go a, go b) of
(Just m, Just n) | m == n -> Just n
_ -> Nothing
countColumnsReturned _ = Just 0
-- TODO INSERT ... RETURNING &c