mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 20:41:49 +03:00
4815fcd500
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
109 lines
4.3 KiB
Haskell
109 lines
4.3 KiB
Haskell
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 doesn’t 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. Shouldn’t 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 doesn’t. We treat null to mean “no condition was specified” (since
|
||
that’s how GraphQL indicates an optional field was omitted), and we actually
|
||
generate SQL like this:
|
||
|
||
DELETE FROM users
|
||
|
||
Now we’ve 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 won’t 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. -}
|