graphql-engine/server/src-lib/Hasura/GraphQL/Schema/BoolExp.hs
Antoine Leblanc 4815fcd500 server: progress on generic metadata
This PR generalizes a bunch of metadata structures.

Most importantly, it changes `SourceCache` to hold existentially quantified values:
```
data BackendSourceInfo =
  forall b. Backend b => BackendSourceInfo (SourceInfo b)

type SourceCache = HashMap SourceName BackendSourceInfo
```

This changes a *lot* of things throughout the code. For now, all code using the schema cache explicitly casts sources to Postgres, meaning that if any non-Postgres `SourceInfo` makes it to the cache, it'll be ignored.

That means that after this PR is submitted, we can split work between two different aspects:
  - creating `SourceInfo` for other backends
  - handling those other sources down the line

GitOrigin-RevId: fb9ea00f32e840fc33c5467896fb1dfa5283ab42
2021-01-20 00:32:45 +00:00

109 lines
4.3 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module Hasura.GraphQL.Schema.BoolExp
( boolExp
) where
import Hasura.Prelude
import qualified Language.GraphQL.Draft.Syntax as G
import Data.Text.Extended
import qualified Hasura.GraphQL.Parser as P
import Hasura.GraphQL.Parser (InputFieldsParser, Kind (..), Parser,
UnpreparedValue)
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Table
import Hasura.RQL.Types
-- |
-- > input type_bool_exp {
-- > _or: [type_bool_exp!]
-- > _and: [type_bool_exp!]
-- > _not: type_bool_exp
-- > column: type_comparison_exp
-- > ...
-- > }
boolExp
:: forall m n r b. (BackendSchema b, MonadSchema n m, MonadTableInfo r m, MonadRole r m)
=> TableName b
-> Maybe (SelPermInfo b)
-> m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
boolExp table selectPermissions = memoizeOn 'boolExp table $ do
tableGQLName <- getTableGQLName @b table
let name = tableGQLName <> $$(G.litName "_bool_exp")
let description = G.Description $
"Boolean expression to filter rows from the table " <> table <<>
". All fields are combined with a logical 'AND'."
tableFieldParsers <- catMaybes <$> maybe
(pure [])
(traverse mkField <=< tableSelectFields table)
selectPermissions
recur <- boolExp table selectPermissions
-- Bafflingly, ApplicativeDo doesnt work if we inline this definition (I
-- think the TH splices throw it off), so we have to define it separately.
let specialFieldParsers =
[ P.fieldOptional $$(G.litName "_or") Nothing (BoolOr <$> P.list recur)
, P.fieldOptional $$(G.litName "_and") Nothing (BoolAnd <$> P.list recur)
, P.fieldOptional $$(G.litName "_not") Nothing (BoolNot <$> recur)
]
pure $ BoolAnd <$> P.object name (Just description) do
tableFields <- map BoolFld . catMaybes <$> sequenceA tableFieldParsers
specialFields <- catMaybes <$> sequenceA specialFieldParsers
pure (tableFields ++ specialFields)
where
mkField
:: FieldInfo b
-> m (Maybe (InputFieldsParser n (Maybe (AnnBoolExpFld b (UnpreparedValue b)))))
mkField fieldInfo = runMaybeT do
fieldName <- hoistMaybe $ fieldInfoGraphQLName fieldInfo
P.fieldOptional fieldName Nothing <$> case fieldInfo of
-- field_name: field_type_comparison_exp
FIColumn columnInfo ->
lift $ fmap (AVCol columnInfo) <$> comparisonExps @b (pgiType columnInfo)
-- field_name: field_type_bool_exp
FIRelationship relationshipInfo -> do
let remoteTable = riRTable relationshipInfo
remotePermissions <- lift $ tableSelectPermissions remoteTable
lift $ fmap (AVRel relationshipInfo) <$> boolExp remoteTable remotePermissions
-- Using computed fields in boolean expressions is not currently supported.
FIComputedField _ -> empty
-- Using remote relationship fields in boolean expressions is not supported.
FIRemoteRelationship _ -> empty
{- Note [Columns in comparison expression are never nullable]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In comparisonExps, we hardcode `Nullability False` when calling `column`, which
might seem a bit sketchy. Shouldnt the nullability depend on the nullability of
the underlying Postgres column?
No. If we did that, then we would allow boolean expressions like this:
delete_users(where: {status: {eq: null}})
The user expects this to generate SQL like
DELETE FROM users WHERE users.status IS NULL
but it doesnt. We treat null to mean “no condition was specified” (since
thats how GraphQL indicates an optional field was omitted), and we actually
generate SQL like this:
DELETE FROM users
Now weve gone and deleted every user in the database. Hopefully the user had
backups!
We avoid this problem by making the column value non-nullable (which is correct,
since we never treat a null value as a SQL NULL), then creating the field using
fieldOptional. This creates a parser that rejects nulls, but wont be called at
all if the field is not specified, which is permitted by the GraphQL
specification. See Note [Optional fields and nullability] in
Hasura.GraphQL.Parser.Internal.Parser for more details. -}