2021-03-18 23:34:11 +03:00
|
|
|
-- | This module defines all basic Template Haskell functions we use in the rest
|
|
|
|
-- of this folder, to generate code that deals with all possible known
|
|
|
|
-- backends.
|
|
|
|
--
|
|
|
|
-- Those are all "normal" Haskell functions in the @Q@ monad: they deal with
|
|
|
|
-- values that represent Haskell code. Those functions are used, in other
|
|
|
|
-- modules, within Template Haskell splices.
|
|
|
|
--
|
|
|
|
-- For more information about Template Haskell:
|
|
|
|
-- * this tutorial is an incredible resource: https://markkarpov.com/tutorial/th.html
|
|
|
|
-- * the definition of the AST is fairly readable:
|
|
|
|
-- * Exp: https://hackage.haskell.org/package/template-haskell-2.16.0.0/docs/Language-Haskell-TH.html#t:Exp
|
|
|
|
-- * Dec: https://hackage.haskell.org/package/template-haskell-2.16.0.0/docs/Language-Haskell-TH.html#t:Dec
|
|
|
|
module Hasura.SQL.TH
|
2021-09-24 01:56:37 +03:00
|
|
|
( backendConstructors,
|
|
|
|
forEachBackend,
|
|
|
|
getBackendValue,
|
|
|
|
getBackendTypeValue,
|
|
|
|
getBackendTagName,
|
|
|
|
getBackendValueName,
|
|
|
|
backendList,
|
|
|
|
backendCase,
|
|
|
|
backendData,
|
|
|
|
mkDispatch,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.SQL.Backend
|
|
|
|
import Language.Haskell.TH
|
2021-03-18 23:34:11 +03:00
|
|
|
|
2021-07-07 04:43:42 +03:00
|
|
|
type BackendConstructor = NonEmpty Name
|
2021-04-22 00:44:37 +03:00
|
|
|
|
|
|
|
-- | Inspects the 'BackendType' to produce a list of its constructors in the 'Q' monad. Each
|
|
|
|
-- constructor is represented as a list of names, to include the arguments, if any.
|
|
|
|
-- This assumes that the arguments themselves don't have arguments.
|
|
|
|
backendConstructors :: Q [BackendConstructor]
|
2021-03-18 23:34:11 +03:00
|
|
|
backendConstructors = do
|
|
|
|
TyConI (DataD _ _ _ _ cons _) <- reify ''BackendType
|
2021-04-22 00:44:37 +03:00
|
|
|
concat <$> for cons \con -> do
|
|
|
|
-- We pattern match in the monad to rely on 'fail'.
|
|
|
|
NormalC name args <- pure con
|
|
|
|
argsConstructors <- for args \(_, arg) -> do
|
|
|
|
ConT argName <- pure arg
|
|
|
|
TyConI (DataD _ _ _ _ argCons _) <- reify argName
|
|
|
|
pure [argCon | NormalC argCon _ <- argCons]
|
2021-07-07 04:43:42 +03:00
|
|
|
pure $ map (name :|) $ sequenceA argsConstructors
|
2021-03-18 23:34:11 +03:00
|
|
|
|
|
|
|
-- | Associates a value in the 'Q' monad to each backend @Name@.
|
2021-04-22 00:44:37 +03:00
|
|
|
forEachBackend :: (BackendConstructor -> Q a) -> Q [a]
|
2021-03-18 23:34:11 +03:00
|
|
|
forEachBackend f = traverse f =<< backendConstructors
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
-- | Associates to a backend the promoted type level value.
|
|
|
|
-- getBackendValue ["Postgres", "Vanilla"] = [| Postgres Vanilla |]
|
|
|
|
getBackendValue :: BackendConstructor -> Exp
|
|
|
|
getBackendValue backend = foldl1 AppE $ ConE <$> backend
|
|
|
|
|
|
|
|
-- | Associates to a backend the promoted type level value.
|
|
|
|
-- getBackendValue ["Postgres", "Vanilla"] = [t| ('Postgres 'Vanilla) |]
|
|
|
|
getBackendTypeValue :: BackendConstructor -> Type
|
|
|
|
getBackendTypeValue backend = ParensT $ foldl1 AppT $ PromotedT <$> backend
|
|
|
|
|
2021-03-18 23:34:11 +03:00
|
|
|
-- | Associates to a backend the Name of its corresponding tag.
|
2021-04-22 00:44:37 +03:00
|
|
|
getBackendTagName :: BackendConstructor -> Name
|
|
|
|
getBackendTagName backend = mkName $ concatMap nameBase backend ++ "Tag"
|
2021-03-18 23:34:11 +03:00
|
|
|
|
|
|
|
-- | Associates to a backend the Name of its corresponding 'AnyBackend' constructor.
|
2021-04-22 00:44:37 +03:00
|
|
|
getBackendValueName :: BackendConstructor -> Name
|
|
|
|
getBackendValueName backend = mkName $ concatMap nameBase backend ++ "Value"
|
2021-03-18 23:34:11 +03:00
|
|
|
|
|
|
|
-- | Creates a list of values by associating an expression to each backend.
|
2021-04-22 00:44:37 +03:00
|
|
|
backendList :: (BackendConstructor -> Q Exp) -> Q Exp
|
2021-03-18 23:34:11 +03:00
|
|
|
backendList f = ListE <$> forEachBackend f
|
|
|
|
|
|
|
|
-- | Creates a case expression with a match for each backend. It is not possible
|
|
|
|
-- do directly expand a @Q [Match]@, which is a body of a case, hence the need
|
|
|
|
-- to instead generate the full @Q Exp@.
|
2021-09-24 01:56:37 +03:00
|
|
|
backendCase ::
|
|
|
|
-- | the expresion on which we do a case switch
|
|
|
|
Q Exp ->
|
|
|
|
-- | the match pattern for a given backend
|
|
|
|
(BackendConstructor -> Q Pat) ->
|
|
|
|
-- | the match body for a given backend
|
|
|
|
(BackendConstructor -> Q Exp) ->
|
|
|
|
-- | the default case, if any
|
|
|
|
Maybe (Q Exp) ->
|
|
|
|
Q Exp
|
2021-03-18 23:34:11 +03:00
|
|
|
backendCase caseExp toPat toBody defaultCase = do
|
2021-09-24 01:56:37 +03:00
|
|
|
cexp <- caseExp
|
2021-03-18 23:34:11 +03:00
|
|
|
matches <- forEachBackend \b -> do
|
2021-09-24 01:56:37 +03:00
|
|
|
pat <- toPat b
|
2021-03-18 23:34:11 +03:00
|
|
|
body <- toBody b
|
|
|
|
pure $ Match pat (NormalB body) []
|
|
|
|
allMatches <- case defaultCase of
|
|
|
|
Nothing -> pure matches
|
2021-09-24 01:56:37 +03:00
|
|
|
Just e -> do
|
2021-03-18 23:34:11 +03:00
|
|
|
defaultBody <- NormalB <$> e
|
|
|
|
pure $ matches ++ [Match WildP defaultBody []]
|
|
|
|
pure $ CaseE cexp allMatches
|
|
|
|
|
server: fix major issue with JSON instances of AnyBackend
### Description
This PR fixes a major issue in the JSON instances of `AnyBackend`: they were not symmetrical! `FromJSON` always made the assumption that the value was an object, and that it contained a "kind" field if it happened to not be a Postgres value. `ToJSON` did NOT insert said field in the output, and did not enforce that the output was an object.
....however, it worked, because nowhere in the code did we yet rely on those being symmetrical. They are both used only once:
- `parseJSON` was used to decode a `Metadata` object, but the matching `toJSON` instance, which is heavily customized, does insert the "kind" field properly
- `toJSON` was only used on the `SchemaCache`, which has no corresponding `FromJSON` instance, since we only serialize it in debug endpoints
This PR makes no attempt at making the instances symmetrical. Instead, it implements simpler functions, and pushes the problem of identifying the proper backend (if any) to the call sites.
### Notes
Additionally, it cleans up some instances that were manually written where they could be auto-generated. In the process, this PR changes the semantics of `Show`, since the stock derived instance will include the constructor name, where before it was skipped. I think it is preferable.
https://github.com/hasura/graphql-engine-mono/pull/1672
GitOrigin-RevId: 0a1580a0e0f01c25b8c9fee7612dba6e7de055d5
2021-06-28 21:38:12 +03:00
|
|
|
-- | Creates a data type in which there's one constructor per backend. While
|
2021-03-18 23:34:11 +03:00
|
|
|
-- this only returns one declaration, it nonetheless returns a @[Dec]@ as it's
|
|
|
|
-- what the $() splice interpolation syntax expects.
|
2021-09-24 01:56:37 +03:00
|
|
|
backendData ::
|
|
|
|
-- | the name of the type
|
|
|
|
Name ->
|
|
|
|
-- | type variables of the type if any
|
|
|
|
[TyVarBndr] ->
|
|
|
|
-- | the constructor for a given backend
|
|
|
|
(BackendConstructor -> Q Con) ->
|
|
|
|
-- | classes to derive using the stock strategy
|
|
|
|
[Name] ->
|
|
|
|
Q [Dec]
|
server: fix major issue with JSON instances of AnyBackend
### Description
This PR fixes a major issue in the JSON instances of `AnyBackend`: they were not symmetrical! `FromJSON` always made the assumption that the value was an object, and that it contained a "kind" field if it happened to not be a Postgres value. `ToJSON` did NOT insert said field in the output, and did not enforce that the output was an object.
....however, it worked, because nowhere in the code did we yet rely on those being symmetrical. They are both used only once:
- `parseJSON` was used to decode a `Metadata` object, but the matching `toJSON` instance, which is heavily customized, does insert the "kind" field properly
- `toJSON` was only used on the `SchemaCache`, which has no corresponding `FromJSON` instance, since we only serialize it in debug endpoints
This PR makes no attempt at making the instances symmetrical. Instead, it implements simpler functions, and pushes the problem of identifying the proper backend (if any) to the call sites.
### Notes
Additionally, it cleans up some instances that were manually written where they could be auto-generated. In the process, this PR changes the semantics of `Show`, since the stock derived instance will include the constructor name, where before it was skipped. I think it is preferable.
https://github.com/hasura/graphql-engine-mono/pull/1672
GitOrigin-RevId: 0a1580a0e0f01c25b8c9fee7612dba6e7de055d5
2021-06-28 21:38:12 +03:00
|
|
|
backendData name tVars mkCon derivs = do
|
2021-03-18 23:34:11 +03:00
|
|
|
constructors <- forEachBackend mkCon
|
server: fix major issue with JSON instances of AnyBackend
### Description
This PR fixes a major issue in the JSON instances of `AnyBackend`: they were not symmetrical! `FromJSON` always made the assumption that the value was an object, and that it contained a "kind" field if it happened to not be a Postgres value. `ToJSON` did NOT insert said field in the output, and did not enforce that the output was an object.
....however, it worked, because nowhere in the code did we yet rely on those being symmetrical. They are both used only once:
- `parseJSON` was used to decode a `Metadata` object, but the matching `toJSON` instance, which is heavily customized, does insert the "kind" field properly
- `toJSON` was only used on the `SchemaCache`, which has no corresponding `FromJSON` instance, since we only serialize it in debug endpoints
This PR makes no attempt at making the instances symmetrical. Instead, it implements simpler functions, and pushes the problem of identifying the proper backend (if any) to the call sites.
### Notes
Additionally, it cleans up some instances that were manually written where they could be auto-generated. In the process, this PR changes the semantics of `Show`, since the stock derived instance will include the constructor name, where before it was skipped. I think it is preferable.
https://github.com/hasura/graphql-engine-mono/pull/1672
GitOrigin-RevId: 0a1580a0e0f01c25b8c9fee7612dba6e7de055d5
2021-06-28 21:38:12 +03:00
|
|
|
pure [DataD [] name tVars Nothing constructors [DerivClause (Just StockStrategy) $ map ConT derivs]]
|
2021-03-18 23:34:11 +03:00
|
|
|
|
|
|
|
-- | Generates a case expression that applies a function @f@ to each possible value
|
|
|
|
-- of an 'AnyBackend' @e@:
|
|
|
|
--
|
|
|
|
-- case e of
|
|
|
|
-- FooValue x -> f x
|
|
|
|
-- BarValue x -> f x
|
|
|
|
--
|
|
|
|
-- This function needs to be in a separate file from 'AnyBackend', so that it
|
|
|
|
-- can be used in splices of another module.
|
2021-09-24 01:56:37 +03:00
|
|
|
mkDispatch ::
|
|
|
|
-- | the name of the function to dispatch
|
|
|
|
Name ->
|
|
|
|
-- | the name of the 'AnyBackend' value
|
|
|
|
Name ->
|
|
|
|
Q Exp
|
2021-03-18 23:34:11 +03:00
|
|
|
mkDispatch func value = do
|
|
|
|
let fE = pure $ VarE func
|
|
|
|
vE = pure $ VarE value
|
2021-09-24 01:56:37 +03:00
|
|
|
backendCase
|
|
|
|
[|$vE|]
|
2021-03-18 23:34:11 +03:00
|
|
|
-- the pattern for a backend
|
|
|
|
(\b -> pure $ ConP (getBackendValueName b) [VarP $ mkName "x"])
|
|
|
|
-- the body for a backend
|
2021-09-24 01:56:37 +03:00
|
|
|
(const [|$fE x|])
|
2021-03-18 23:34:11 +03:00
|
|
|
-- no default case
|
|
|
|
Nothing
|