mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 20:41:49 +03:00
c4536e801c
Prior to this change, the SQL expression that resulted from translating permissions on functions would refer to the table of the function's return type, rather than the set of rows selected from the function being called. Now the SQL that results from translating permissions correctly refer to the selected rows. This PR also contains the suggested additions of https://github.com/hasura/graphql-engine-mono/pull/2563#discussion_r726116863, which simplifies the Boolean Expression IR, but in turn makes the Schema Dependency Discovery algorithm work a bit harder. We are changing the definition of `data OpExpG`, but the format accepted by its JSON parser remains unchanged. While there does exist a generically derived `instance ToJSON OpExpG` this is only used in the (unpublished) `/v1/metadata/dump_internal_state` API. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2609 Co-authored-by: Gil Mizrahi <8547573+soupi@users.noreply.github.com> GitOrigin-RevId: bb9a0b4addbc239499dd2268909220196984df72
285 lines
14 KiB
Haskell
285 lines
14 KiB
Haskell
module Hasura.Backends.Postgres.DDL.BoolExp
|
|
( parseBoolExpOperations,
|
|
)
|
|
where
|
|
|
|
import Data.Aeson
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.Text qualified as T
|
|
import Data.Text.Extended
|
|
import Hasura.Backends.Postgres.SQL.Types
|
|
import Hasura.Backends.Postgres.Types.BoolExp
|
|
import Hasura.Base.Error
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.IR.BoolExp
|
|
import Hasura.RQL.Types.Backend
|
|
import Hasura.RQL.Types.Column
|
|
import Hasura.RQL.Types.SchemaCache
|
|
import Hasura.RQL.Types.Table
|
|
import Hasura.SQL.Backend
|
|
import Hasura.SQL.Types
|
|
|
|
parseBoolExpOperations ::
|
|
forall pgKind m v.
|
|
( Backend ('Postgres pgKind),
|
|
MonadError QErr m,
|
|
TableCoreInfoRM ('Postgres pgKind) m
|
|
) =>
|
|
ValueParser ('Postgres pgKind) m v ->
|
|
QualifiedTable ->
|
|
FieldInfoMap (FieldInfo ('Postgres pgKind)) ->
|
|
ColumnReference ('Postgres pgKind) ->
|
|
Value ->
|
|
m [OpExpG ('Postgres pgKind) v]
|
|
parseBoolExpOperations rhsParser rootTable fim columnRef value = do
|
|
restrictJSONColumn
|
|
withPathK (toTxt columnRef) $ parseOperations columnRef value
|
|
where
|
|
restrictJSONColumn :: m ()
|
|
restrictJSONColumn = case columnReferenceType columnRef of
|
|
ColumnScalar PGJSON ->
|
|
throwError (err400 UnexpectedPayload "JSON column can not be part of boolean expression")
|
|
_ -> pure ()
|
|
|
|
parseOperations :: ColumnReference ('Postgres pgKind) -> Value -> m [OpExpG ('Postgres pgKind) v]
|
|
parseOperations column = \case
|
|
Object o -> mapM (parseOperation column) (Map.toList o)
|
|
val -> pure . AEQ False <$> rhsParser columnType val
|
|
where
|
|
columnType = CollectableTypeScalar $ columnReferenceType column
|
|
|
|
parseOperation :: ColumnReference ('Postgres pgKind) -> (Text, Value) -> m (OpExpG ('Postgres pgKind) v)
|
|
parseOperation column (opStr, val) = withPathK opStr $
|
|
case opStr of
|
|
"$cast" -> parseCast
|
|
"_cast" -> parseCast
|
|
"$eq" -> parseEq
|
|
"_eq" -> parseEq
|
|
"$ne" -> parseNe
|
|
"_ne" -> parseNe
|
|
"$neq" -> parseNe
|
|
"_neq" -> parseNe
|
|
"$in" -> parseIn
|
|
"_in" -> parseIn
|
|
"$nin" -> parseNin
|
|
"_nin" -> parseNin
|
|
"$gt" -> parseGt
|
|
"_gt" -> parseGt
|
|
"$lt" -> parseLt
|
|
"_lt" -> parseLt
|
|
"$gte" -> parseGte
|
|
"_gte" -> parseGte
|
|
"$lte" -> parseLte
|
|
"_lte" -> parseLte
|
|
"$like" -> parseLike
|
|
"_like" -> parseLike
|
|
"$nlike" -> parseNlike
|
|
"_nlike" -> parseNlike
|
|
"$ilike" -> parseIlike
|
|
"_ilike" -> parseIlike
|
|
"$nilike" -> parseNilike
|
|
"_nilike" -> parseNilike
|
|
"$similar" -> parseSimilar
|
|
"_similar" -> parseSimilar
|
|
"$nsimilar" -> parseNsimilar
|
|
"_nsimilar" -> parseNsimilar
|
|
"$regex" -> parseRegex
|
|
"_regex" -> parseRegex
|
|
"$iregex" -> parseIRegex
|
|
"_iregex" -> parseIRegex
|
|
"$nregex" -> parseNRegex
|
|
"_nregex" -> parseNRegex
|
|
"$niregex" -> parseNIRegex
|
|
"_niregex" -> parseNIRegex
|
|
"$is_null" -> parseIsNull
|
|
"_is_null" -> parseIsNull
|
|
-- jsonb type
|
|
"_contains" -> guardType [PGJSONB] >> ABackendSpecific . AContains <$> parseOne
|
|
"$contains" -> guardType [PGJSONB] >> ABackendSpecific . AContains <$> parseOne
|
|
"_contained_in" -> guardType [PGJSONB] >> ABackendSpecific . AContainedIn <$> parseOne
|
|
"$contained_in" -> guardType [PGJSONB] >> ABackendSpecific . AContainedIn <$> parseOne
|
|
"_has_key" -> guardType [PGJSONB] >> ABackendSpecific . AHasKey <$> parseWithTy (ColumnScalar PGText)
|
|
"$has_key" -> guardType [PGJSONB] >> ABackendSpecific . AHasKey <$> parseWithTy (ColumnScalar PGText)
|
|
"_has_keys_any" -> guardType [PGJSONB] >> ABackendSpecific . AHasKeysAny <$> parseManyWithType (ColumnScalar PGText)
|
|
"$has_keys_any" -> guardType [PGJSONB] >> ABackendSpecific . AHasKeysAny <$> parseManyWithType (ColumnScalar PGText)
|
|
"_has_keys_all" -> guardType [PGJSONB] >> ABackendSpecific . AHasKeysAll <$> parseManyWithType (ColumnScalar PGText)
|
|
"$has_keys_all" -> guardType [PGJSONB] >> ABackendSpecific . AHasKeysAll <$> parseManyWithType (ColumnScalar PGText)
|
|
-- geometry types
|
|
"_st_contains" -> parseGeometryOp ASTContains
|
|
"$st_contains" -> parseGeometryOp ASTContains
|
|
"_st_crosses" -> parseGeometryOp ASTCrosses
|
|
"$st_crosses" -> parseGeometryOp ASTCrosses
|
|
"_st_equals" -> parseGeometryOp ASTEquals
|
|
"$st_equals" -> parseGeometryOp ASTEquals
|
|
"_st_overlaps" -> parseGeometryOp ASTOverlaps
|
|
"$st_overlaps" -> parseGeometryOp ASTOverlaps
|
|
"_st_touches" -> parseGeometryOp ASTTouches
|
|
"$st_touches" -> parseGeometryOp ASTTouches
|
|
"_st_within" -> parseGeometryOp ASTWithin
|
|
"$st_within" -> parseGeometryOp ASTWithin
|
|
-- geometry and geography types
|
|
"_st_intersects" -> parseGeometryOrGeographyOp ASTIntersects
|
|
"$st_intersects" -> parseGeometryOrGeographyOp ASTIntersects
|
|
"_st_3d_intersects" -> parseGeometryOp AST3DIntersects
|
|
"$st_3d_intersects" -> parseGeometryOp AST3DIntersects
|
|
"_st_d_within" -> parseSTDWithinObj
|
|
"$st_d_within" -> parseSTDWithinObj
|
|
"_st_3d_d_within" -> parseST3DDWithinObj
|
|
"$st_3d_d_within" -> parseST3DDWithinObj
|
|
"$ceq" -> parseCeq
|
|
"_ceq" -> parseCeq
|
|
"$cne" -> parseCne
|
|
"_cne" -> parseCne
|
|
"$cneq" -> parseCne
|
|
"_cneq" -> parseCne
|
|
"$cgt" -> parseCgt
|
|
"_cgt" -> parseCgt
|
|
"$clt" -> parseClt
|
|
"_clt" -> parseClt
|
|
"$cgte" -> parseCgte
|
|
"_cgte" -> parseCgte
|
|
"$clte" -> parseClte
|
|
"_clte" -> parseClte
|
|
-- ltree types
|
|
"_ancestor" -> guardType [PGLtree] >> ABackendSpecific . AAncestor <$> parseOne
|
|
"$ancestor" -> guardType [PGLtree] >> ABackendSpecific . AAncestor <$> parseOne
|
|
"_ancestor_any" -> guardType [PGLtree] >> ABackendSpecific . AAncestorAny <$> parseManyWithType (ColumnScalar PGLtree)
|
|
"$ancestor_any" -> guardType [PGLtree] >> ABackendSpecific . AAncestorAny <$> parseManyWithType (ColumnScalar PGLtree)
|
|
"_descendant" -> guardType [PGLtree] >> ABackendSpecific . ADescendant <$> parseOne
|
|
"$descendant" -> guardType [PGLtree] >> ABackendSpecific . ADescendant <$> parseOne
|
|
"_descendant_any" -> guardType [PGLtree] >> ABackendSpecific . ADescendantAny <$> parseManyWithType (ColumnScalar PGLtree)
|
|
"$descendant_any" -> guardType [PGLtree] >> ABackendSpecific . ADescendantAny <$> parseManyWithType (ColumnScalar PGLtree)
|
|
"_matches" -> guardType [PGLtree] >> ABackendSpecific . AMatches <$> parseWithTy (ColumnScalar PGLquery)
|
|
"$matches" -> guardType [PGLtree] >> ABackendSpecific . AMatches <$> parseWithTy (ColumnScalar PGLquery)
|
|
"_matches_any" -> guardType [PGLtree] >> ABackendSpecific . AMatchesAny <$> parseManyWithType (ColumnScalar PGLquery)
|
|
"$matches_any" -> guardType [PGLtree] >> ABackendSpecific . AMatchesAny <$> parseManyWithType (ColumnScalar PGLquery)
|
|
"_matches_fulltext" -> guardType [PGLtree] >> ABackendSpecific . AMatchesFulltext <$> parseWithTy (ColumnScalar PGLtxtquery)
|
|
"$matches_fulltext" -> guardType [PGLtree] >> ABackendSpecific . AMatchesFulltext <$> parseWithTy (ColumnScalar PGLtxtquery)
|
|
x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x
|
|
where
|
|
colTy = columnReferenceType column
|
|
|
|
parseIsNull = bool ANISNOTNULL ANISNULL <$> parseVal -- is null
|
|
parseEq = AEQ False <$> parseOne -- equals
|
|
parseNe = ANE False <$> parseOne -- <>
|
|
parseIn = AIN <$> parseManyWithType colTy -- in an array
|
|
parseNin = ANIN <$> parseManyWithType colTy -- not in an array
|
|
parseGt = AGT <$> parseOne -- >
|
|
parseLt = ALT <$> parseOne -- <
|
|
parseGte = AGTE <$> parseOne -- >=
|
|
parseLte = ALTE <$> parseOne -- <=
|
|
parseCeq = CEQ <$> decodeAndValidateRhsCol val
|
|
parseCne = CNE <$> decodeAndValidateRhsCol val
|
|
parseCgt = CGT <$> decodeAndValidateRhsCol val
|
|
parseClt = CLT <$> decodeAndValidateRhsCol val
|
|
parseCgte = CGTE <$> decodeAndValidateRhsCol val
|
|
parseClte = CLTE <$> decodeAndValidateRhsCol val
|
|
|
|
parseLike = guardType stringTypes >> ALIKE <$> parseOne
|
|
parseNlike = guardType stringTypes >> ANLIKE <$> parseOne
|
|
parseIlike = guardType stringTypes >> ABackendSpecific . AILIKE <$> parseOne
|
|
parseNilike = guardType stringTypes >> ABackendSpecific . ANILIKE <$> parseOne
|
|
|
|
parseRegex = guardType stringTypes >> ABackendSpecific . AREGEX <$> parseOne
|
|
parseIRegex = guardType stringTypes >> ABackendSpecific . AIREGEX <$> parseOne
|
|
parseNRegex = guardType stringTypes >> ABackendSpecific . ANREGEX <$> parseOne
|
|
parseNIRegex = guardType stringTypes >> ABackendSpecific . ANIREGEX <$> parseOne
|
|
parseSimilar = guardType stringTypes >> ABackendSpecific . ASIMILAR <$> parseOne
|
|
parseNsimilar = guardType stringTypes >> ABackendSpecific . ANSIMILAR <$> parseOne
|
|
|
|
parseCast = do
|
|
castOperations <- parseVal
|
|
parsedCastOperations <-
|
|
forM (Map.toList castOperations) $ \(targetTypeName, castedComparisons) -> do
|
|
let targetType = textToPGScalarType targetTypeName
|
|
castedColumn = ColumnReferenceCast column (ColumnScalar targetType)
|
|
checkValidCast targetType
|
|
parsedCastedComparisons <-
|
|
withPathK targetTypeName $
|
|
parseOperations castedColumn castedComparisons
|
|
return (targetType, parsedCastedComparisons)
|
|
return . ACast $ Map.fromList parsedCastOperations
|
|
|
|
checkValidCast targetType = case (colTy, targetType) of
|
|
(ColumnScalar PGGeometry, PGGeography) -> return ()
|
|
(ColumnScalar PGGeography, PGGeometry) -> return ()
|
|
_ ->
|
|
throw400 UnexpectedPayload $
|
|
"cannot cast column of type " <> colTy <<> " to type " <>> targetType
|
|
|
|
parseGeometryOp f =
|
|
guardType [PGGeometry] >> ABackendSpecific . f <$> parseOneNoSess colTy val
|
|
parseGeometryOrGeographyOp f =
|
|
guardType geoTypes >> ABackendSpecific . f <$> parseOneNoSess colTy val
|
|
|
|
parseSTDWithinObj =
|
|
ABackendSpecific <$> case colTy of
|
|
ColumnScalar PGGeometry -> do
|
|
DWithinGeomOp distVal fromVal <- parseVal
|
|
dist <- withPathK "distance" $ parseOneNoSess (ColumnScalar PGFloat) distVal
|
|
from <- withPathK "from" $ parseOneNoSess colTy fromVal
|
|
return $ ASTDWithinGeom $ DWithinGeomOp dist from
|
|
ColumnScalar PGGeography -> do
|
|
DWithinGeogOp distVal fromVal sphVal <- parseVal
|
|
dist <- withPathK "distance" $ parseOneNoSess (ColumnScalar PGFloat) distVal
|
|
from <- withPathK "from" $ parseOneNoSess colTy fromVal
|
|
useSpheroid <- withPathK "use_spheroid" $ parseOneNoSess (ColumnScalar PGBoolean) sphVal
|
|
return $ ASTDWithinGeog $ DWithinGeogOp dist from useSpheroid
|
|
_ -> throwError $ buildMsg colTy [PGGeometry, PGGeography]
|
|
|
|
decodeAndValidateRhsCol :: Value -> m (RootOrCurrentColumn ('Postgres pgKind))
|
|
decodeAndValidateRhsCol v = case v of
|
|
String _ -> go IsCurrent fim v
|
|
Array path -> case toList path of
|
|
[] -> throw400 Unexpected "path cannot be empty"
|
|
[col] -> go IsCurrent fim col
|
|
[String "$", col] -> do
|
|
rootTableInfo <-
|
|
lookupTableCoreInfo rootTable
|
|
>>= flip onNothing (throw500 $ "unexpected: " <> rootTable <<> " doesn't exist")
|
|
go IsRoot (_tciFieldInfoMap rootTableInfo) col
|
|
_ -> throw400 NotSupported "Relationship references are not supported in column comparison RHS"
|
|
_ -> throw400 Unexpected "a boolean expression JSON must be either a string or an array"
|
|
where
|
|
go rootInfo fieldsInfoMap columnValue = do
|
|
colName <- decodeValue columnValue
|
|
colInfo <- validateRhsCol fieldsInfoMap colName
|
|
pure $ RootOrCurrentColumn rootInfo colInfo
|
|
|
|
parseST3DDWithinObj =
|
|
ABackendSpecific <$> do
|
|
guardType [PGGeometry]
|
|
DWithinGeomOp distVal fromVal <- parseVal
|
|
dist <- withPathK "distance" $ parseOneNoSess (ColumnScalar PGFloat) distVal
|
|
from <- withPathK "from" $ parseOneNoSess colTy fromVal
|
|
return $ AST3DDWithinGeom $ DWithinGeomOp dist from
|
|
|
|
validateRhsCol fieldInfoMap rhsCol = do
|
|
rhsType <- askColumnType fieldInfoMap rhsCol "column operators can only compare postgres columns"
|
|
when (colTy /= rhsType) $
|
|
throw400 UnexpectedPayload $
|
|
"incompatible column types: "
|
|
<> column <<> " has type "
|
|
<> colTy <<> ", but "
|
|
<> rhsCol <<> " has type " <>> rhsType
|
|
pure rhsCol
|
|
|
|
parseWithTy ty = rhsParser (CollectableTypeScalar ty) val
|
|
|
|
-- parse one with the column's type
|
|
parseOne = parseWithTy colTy
|
|
parseOneNoSess ty = rhsParser (CollectableTypeScalar ty)
|
|
|
|
parseManyWithType ty = rhsParser (CollectableTypeArray ty) val
|
|
|
|
guardType validTys =
|
|
unless (isScalarColumnWhere (`elem` validTys) colTy) $
|
|
throwError $ buildMsg colTy validTys
|
|
buildMsg ty expTys =
|
|
err400 UnexpectedPayload $
|
|
" is of type " <> ty <<> "; this operator works only on columns of type "
|
|
<> T.intercalate "/" (map dquote expTys)
|
|
|
|
parseVal :: (FromJSON a) => m a
|
|
parseVal = decodeValue val
|