support jsonb and postgis operators in permissions (#1461)

* support jsonb and geometry operators on RQL bool exps, close #1408

* add tests for jsonb operators in /v1/query

TODO:-
-> add tests for geometry (postgis) operators

* support parsing session variables for st_d_within and has_key ops

-> Add tests for boolExp operators and select permissions

* improve parsing $st_d_within op's json value logic
This commit is contained in:
Rakesh Emmadi 2019-01-28 23:16:31 +05:30 committed by Shahidh K Muhammed
parent c6e5add28a
commit 3caff9b924
36 changed files with 1504 additions and 115 deletions

View File

@ -83,10 +83,11 @@ parseOpExps annVal = do
parseAsSTDWithinObj obj = do
distanceVal <- onNothing (OMap.lookup "distance" obj) $
throw500 "expected \"distance\" input field in st_d_within_input ty"
distSQL <- uncurry toTxtValue <$> asPGColVal distanceVal
dist <- asPGColVal distanceVal
fromVal <- onNothing (OMap.lookup "from" obj) $
throw500 "expected \"from\" input field in st_d_within_input ty"
ASTDWithin distSQL <$> asPGColVal fromVal
from <- asPGColVal fromVal
return $ ASTDWithin $ WithinOp dist from
parseAsEqOp
:: (MonadError QErr m)

View File

@ -20,6 +20,7 @@ import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
import Hasura.Server.Utils
import Hasura.SQL.Types
import Hasura.SQL.Value (withGeoVal)
import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
@ -182,10 +183,7 @@ getDependentHeaders (BoolExp boolExp) =
| otherwise -> []
_ -> []
parseObject o =
concatMap parseOnlyString (M.elems o)
-- if isRQLOp k
-- then parseOnlyString v
-- else []
concatMap parseValue (M.elems o)
valueParser :: (MonadError QErr m) => PGColType -> Value -> m S.SQLExp
valueParser columnType = \case
@ -198,9 +196,9 @@ valueParser columnType = \case
val -> txtRHSBuilder columnType val
where
curSess = S.SEUnsafe "current_setting('hasura.user')::json"
fromCurSess hdr =
fromCurSess hdr = withAnnTy $ withGeoVal columnType $
S.SEOpApp (S.SQLOp "->>") [curSess, S.SELit $ T.toLower hdr]
`S.SETyAnn` (S.AnnType $ T.pack $ show columnType)
withAnnTy v = S.SETyAnn v $ S.AnnType $ T.pack $ show columnType
injectDefaults :: QualifiedTable -> QualifiedTable -> Q.Query
injectDefaults qv qt =

View File

@ -24,75 +24,111 @@ parseOpExp
-> FieldInfoMap
-> PGColInfo
-> (T.Text, Value) -> m (OpExpG a)
parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = case opStr of
"$eq" -> parseEq
"_eq" -> parseEq
parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $
case opStr of
"$eq" -> parseEq
"_eq" -> parseEq
"$ne" -> parseNe
"_ne" -> parseNe
"$neq" -> parseNe
"_neq" -> parseNe
"$ne" -> parseNe
"_ne" -> parseNe
"$neq" -> parseNe
"_neq" -> parseNe
"$in" -> parseIn
"_in" -> parseIn
"$in" -> parseIn
"_in" -> parseIn
"$nin" -> parseNin
"_nin" -> parseNin
"$nin" -> parseNin
"_nin" -> parseNin
"$gt" -> parseGt
"_gt" -> parseGt
"$gt" -> parseGt
"_gt" -> parseGt
"$lt" -> parseLt
"_lt" -> parseLt
"$lt" -> parseLt
"_lt" -> parseLt
"$gte" -> parseGte
"_gte" -> parseGte
"$gte" -> parseGte
"_gte" -> parseGte
"$lte" -> parseLte
"_lte" -> parseLte
"$lte" -> parseLte
"_lte" -> parseLte
"$like" -> parseLike
"_like" -> parseLike
"$like" -> parseLike
"_like" -> parseLike
"$nlike" -> parseNlike
"_nlike" -> parseNlike
"$nlike" -> parseNlike
"_nlike" -> parseNlike
"$ilike" -> parseIlike
"_ilike" -> parseIlike
"$ilike" -> parseIlike
"_ilike" -> parseIlike
"$nilike" -> parseNilike
"_nilike" -> parseNilike
"$nilike" -> parseNilike
"_nilike" -> parseNilike
"$similar" -> parseSimilar
"_similar" -> parseSimilar
"$nsimilar" -> parseNsimilar
"_nsimilar" -> parseNsimilar
"$similar" -> parseSimilar
"_similar" -> parseSimilar
"$nsimilar" -> parseNsimilar
"_nsimilar" -> parseNsimilar
"$is_null" -> parseIsNull
"_is_null" -> parseIsNull
"$is_null" -> parseIsNull
"_is_null" -> parseIsNull
"$ceq" -> parseCeq
"_ceq" -> parseCeq
-- jsonb type
"_contains" -> jsonbOnlyOp $ AContains <$> parseOne
"$contains" -> jsonbOnlyOp $ AContains <$> parseOne
"_contained_in" -> jsonbOnlyOp $ AContainedIn <$> parseOne
"$contained_in" -> jsonbOnlyOp $ AContainedIn <$> parseOne
"_has_key" -> jsonbOnlyOp $ AHasKey <$> parseWithTy PGText
"$has_key" -> jsonbOnlyOp $ AHasKey <$> parseWithTy PGText
"$cne" -> parseCne
"_cne" -> parseCne
"$cneq" -> parseCne
"_cneq" -> parseCne
--FIXME:- Parse a session variable as text array values
--TODO:- Add following commented operators after fixing above said
-- "_has_keys_any" -> jsonbOnlyOp $ AHasKeysAny <$> parseVal
-- "$has_keys_any" -> jsonbOnlyOp $ AHasKeysAny <$> parseVal
-- "_has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal
-- "$has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal
"$cgt" -> parseCgt
"_cgt" -> parseCgt
-- geometry type
"_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_intersects" -> parseGeometryOp ASTIntersects
"$st_intersects" -> parseGeometryOp ASTIntersects
"_st_overlaps" -> parseGeometryOp ASTOverlaps
"$st_overlaps" -> parseGeometryOp ASTOverlaps
"_st_touches" -> parseGeometryOp ASTTouches
"$st_touches" -> parseGeometryOp ASTTouches
"_st_within" -> parseGeometryOp ASTWithin
"$st_within" -> parseGeometryOp ASTWithin
"_st_d_within" -> parseSTDWithinObj
"$st_d_within" -> parseSTDWithinObj
"$clt" -> parseClt
"_clt" -> parseClt
"$ceq" -> parseCeq
"_ceq" -> parseCeq
"$cgte" -> parseCgte
"_cgte" -> parseCgte
"$cne" -> parseCne
"_cne" -> parseCne
"$cneq" -> parseCne
"_cneq" -> parseCne
"$clte" -> parseClte
"_clte" -> parseClte
"$cgt" -> parseCgt
"_cgt" -> parseCgt
x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x
"$clt" -> parseClt
"_clt" -> parseClt
"$cgte" -> parseCgte
"_cgte" -> parseCgte
"$clte" -> parseClte
"_clte" -> parseClte
x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x
where
withErrPath = withPathK (getPGColTxt cn) . withPathK opStr
parseEq = AEQ False <$> parseOne -- equals
parseNe = ANE False <$> parseOne -- <>
parseIn = AIN <$> parseMany -- in an array
@ -109,7 +145,8 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = case opStr of
parseNsimilar = textOnlyOp colTy >> ANSIMILAR <$> parseOne
parseIsNull = bool ANISNOTNULL ANISNULL -- is null
<$> decodeValue val
<$> parseVal
parseCeq = CEQ <$> decodeAndValidateRhsCol
parseCne = CNE <$> decodeAndValidateRhsCol
parseCgt = CGT <$> decodeAndValidateRhsCol
@ -117,8 +154,21 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = case opStr of
parseCgte = CGTE <$> decodeAndValidateRhsCol
parseClte = CLTE <$> decodeAndValidateRhsCol
jsonbOnlyOp m = case colTy of
PGJSONB -> m
ty -> throwError $ buildMsg ty [PGJSONB]
parseGeometryOp f =
geometryOnlyOp colTy >> f <$> parseOne
parseSTDWithinObj = do
WithinOp distVal fromVal <- parseVal
dist <- withPathK "distance" $ parser PGFloat distVal
from <- withPathK "from" $ parser colTy fromVal
return $ ASTDWithin $ WithinOp dist from
decodeAndValidateRhsCol =
decodeValue val >>= validateRhsCol
parseVal >>= validateRhsCol
validateRhsCol rhsCol = do
let errMsg = "column operators can only compare postgres columns"
@ -128,11 +178,19 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = case opStr of
"incompatible column types : " <> cn <<> ", " <>> rhsCol
else return rhsCol
parseOne = parser colTy val
geometryOnlyOp PGGeometry = return ()
geometryOnlyOp ty =
throwError $ buildMsg ty [PGGeometry]
parseWithTy ty = parser ty val
parseOne = parseWithTy colTy
parseMany = do
vals <- runAesonParser parseJSON val
indexedForM vals (parser colTy)
parseVal :: (FromJSON a, QErrM m) => m a
parseVal = decodeValue val
parseOpExps
:: (MonadError QErr m)
=> ValueParser m a
@ -197,8 +255,8 @@ annColExp valueParser colInfoMap (ColExp fieldName colVal) = do
case colInfo of
FIColumn (PGColInfo _ PGJSON _) ->
throwError (err400 UnexpectedPayload "JSON column can not be part of where clause")
FIColumn (PGColInfo _ PGJSONB _) ->
throwError (err400 UnexpectedPayload "JSONB column can not be part of where clause")
-- FIColumn (PGColInfo _ PGJSONB _) ->
-- throwError (err400 UnexpectedPayload "JSONB column can not be part of where clause")
FIColumn pgi ->
AVCol pgi <$> parseOpExps valueParser colInfoMap pgi colVal
FIRelationship relInfo -> do
@ -252,7 +310,7 @@ txtRHSBuilder
:: (MonadError QErr m)
=> PGColType -> Value -> m S.SQLExp
txtRHSBuilder ty val =
txtEncoder <$> pgValParser ty val
toTxtValue ty <$> pgValParser ty val
mkColCompExp
:: S.Qual -> PGCol -> OpExpG S.SQLExp -> S.BoolExp
@ -279,14 +337,14 @@ mkColCompExp qual lhsCol = \case
AHasKeysAny keys -> S.BECompare S.SHasKeysAny lhs $ toTextArray keys
AHasKeysAll keys -> S.BECompare S.SHasKeysAll lhs $ toTextArray keys
ASTContains val -> mkGeomOpBe "ST_Contains" val
ASTCrosses val -> mkGeomOpBe "ST_Crosses" val
ASTDWithin r val -> applySQLFn "ST_DWithin" [lhs, val, r]
ASTEquals val -> mkGeomOpBe "ST_Equals" val
ASTIntersects val -> mkGeomOpBe "ST_Intersects" val
ASTOverlaps val -> mkGeomOpBe "ST_Overlaps" val
ASTTouches val -> mkGeomOpBe "ST_Touches" val
ASTWithin val -> mkGeomOpBe "ST_Within" val
ASTContains val -> mkGeomOpBe "ST_Contains" val
ASTCrosses val -> mkGeomOpBe "ST_Crosses" val
ASTEquals val -> mkGeomOpBe "ST_Equals" val
ASTIntersects val -> mkGeomOpBe "ST_Intersects" val
ASTOverlaps val -> mkGeomOpBe "ST_Overlaps" val
ASTTouches val -> mkGeomOpBe "ST_Touches" val
ASTWithin val -> mkGeomOpBe "ST_Within" val
ASTDWithin (WithinOp r val) -> applySQLFn "ST_DWithin" [lhs, val, r]
ANISNULL -> S.BENull lhs
ANISNOTNULL -> S.BENotNull lhs

View File

@ -51,6 +51,7 @@ import Hasura.RQL.Types.Permission as R
import Hasura.RQL.Types.RemoteSchema as R
import Hasura.RQL.Types.SchemaCache as R
import Hasura.RQL.Types.Subscribe as R
import Hasura.SQL.Types
import qualified Hasura.GraphQL.Context as GC

View File

@ -4,6 +4,7 @@ module Hasura.RQL.Types.BoolExp
, gBoolExpToJSON
, parseGBoolExp
, WithinOp(..)
, OpExpG(..)
, AnnBoolExpFld(..)
@ -22,7 +23,9 @@ import qualified Hasura.SQL.DML as S
import Hasura.SQL.Types
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.Internal
import Data.Aeson.TH
import qualified Data.Aeson.Types as J
import qualified Data.HashMap.Strict as M
import Instances.TH.Lift ()
@ -90,6 +93,13 @@ foldBoolExp f (BoolNot notExp) =
foldBoolExp f (BoolFld ce) =
f ce
data WithinOp a =
WithinOp
{ woDistance :: !a
, woFrom :: !a
} deriving (Show, Eq, Functor, Foldable, Traversable)
$(deriveJSON (aesonDrop 2 snakeCase) ''WithinOp)
data OpExpG a
= AEQ !Bool !a
| ANE !Bool !a
@ -119,7 +129,7 @@ data OpExpG a
| ASTContains !a
| ASTCrosses !a
| ASTDWithin !S.SQLExp !a
| ASTDWithin !(WithinOp a)
| ASTEquals !a
| ASTIntersects !a
| ASTOverlaps !a
@ -168,7 +178,7 @@ opExpToJPair f = \case
ASTContains a -> ("_st_contains", f a)
ASTCrosses a -> ("_st_crosses", f a)
ASTDWithin _ a -> ("_st_d_within", f a)
ASTDWithin o -> ("_st_d_within", toJSON $ f <$> o)
ASTEquals a -> ("_st_equals", f a)
ASTIntersects a -> ("_st_intersects", f a)
ASTOverlaps a -> ("_st_overlaps", f a)

View File

@ -188,29 +188,27 @@ iresToEither (ISuccess a) = return a
pgValFromJVal :: (FromJSON a) => Value -> Either String a
pgValFromJVal = iresToEither . ifromJSON
applyGeomFromGeoJson :: S.SQLExp -> S.SQLExp
applyGeomFromGeoJson v =
S.SEFnApp "ST_GeomFromGeoJSON" [v] Nothing
withGeoVal :: PGColType -> S.SQLExp -> S.SQLExp
withGeoVal ty v =
bool v applyGeomFromGeoJson isGeoTy
where
applyGeomFromGeoJson =
S.SEFnApp "ST_GeomFromGeoJSON" [v] Nothing
isGeoTy :: PGColType -> Bool
isGeoTy = \case
PGGeometry -> True
PGGeography -> True
_ -> False
isGeoTy = case ty of
PGGeometry -> True
PGGeography -> True
_ -> False
toPrepParam :: Int -> PGColType -> S.SQLExp
toPrepParam i =
bool prepVal (applyGeomFromGeoJson prepVal) . isGeoTy
where
prepVal = S.SEPrep i
toPrepParam i ty =
withGeoVal ty $ S.SEPrep i
toTxtValue :: PGColType -> PGColValue -> S.SQLExp
toTxtValue ty val =
S.annotateExp txtVal ty
where
txtVal = withGeoVal $ txtEncoder val
withGeoVal v =
bool v (applyGeomFromGeoJson v) $ isGeoTy ty
txtVal = withGeoVal ty $ txtEncoder val
pgColValueToInt :: PGColValue -> Maybe Int
pgColValueToInt (PGValInteger i) = Just $ fromIntegral i

View File

@ -260,4 +260,133 @@ args:
name: search_tracks
schema: public
#Permission based on PostGIS operators
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis;
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis_topology;
#Create table
- type: run_sql
args:
sql: |
CREATE TABLE geom_table(
id SERIAL PRIMARY KEY,
type TEXT NOT NULL,
geom_col geometry NOT NULL
);
- type: track_table
args:
name: geom_table
schema: public
#Create select permission using postgis operator
- type: create_select_permission
args:
table: geom_table
role: user1
permission:
columns:
- id
- type
- geom_col
filter:
geom_col:
$st_d_within:
distance: 1
from:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
#Create select permission using postgis operator and session variables
- type: create_select_permission
args:
table: geom_table
role: user2
permission:
columns:
- id
- type
- geom_col
filter:
geom_col:
$st_d_within:
distance: X-Hasura-Geom-Dist
from: X-Hasura-Geom-Val
#Insert data
- type: run_sql
args:
sql: |
INSERT INTO geom_table (type, geom_col)
VALUES
('point', ST_GeomFromText('POINT(1 2)')),
('linestring', ST_GeomFromText('LINESTRING(0 0, 0.5 1, 1 2, 1.5 3)')),
('linestring', ST_GeomFromText('LINESTRING(1 0, 0.5 0.5, 0 1)')),
('polygon', ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')),
('polygon', ST_GeomFromText('POLYGON((2 0, 2 1, 3 1, 3 0, 2 0))'))
;
#Permission based on JSONB operators
- type: run_sql
args:
sql: |
CREATE TABLE jsonb_table(
id SERIAL PRIMARY KEY,
jsonb_col jsonb NOT NULL
);
- type: track_table
args:
name: jsonb_table
schema: public
#Create select permission using jsonb operator
- type: create_select_permission
args:
table: jsonb_table
role: user1
permission:
columns:
- id
- jsonb_col
filter:
jsonb_col:
$has_key: age
- type: create_select_permission
args:
table: jsonb_table
role: user2
permission:
columns:
- id
- jsonb_col
filter:
jsonb_col:
$has_key: X-Hasura-Has-Key
#Insert data
- type: insert
args:
table: jsonb_table
objects:
- jsonb_col:
name: Hasura
age: 7
- jsonb_col:
name: Cross

View File

@ -1,26 +1,12 @@
type: bulk
args:
- type: run_sql
args:
sql: |
drop table article
cascade: true
- type: run_sql
args:
sql: |
drop table author
cascade: true
- type: run_sql
args:
sql: |
drop table "Track" cascade
cascade: true
- type: run_sql
args:
sql: |
drop table "Artist"
DROP TABLE article;
DROP TABLE author;
DROP TABLE "Track" cascade;
DROP TABLE "Artist";
DROP TABLE geom_table;
DROP TABLE jsonb_table;
cascade: true

View File

@ -0,0 +1,59 @@
description: User can query geometry values which satisfies filter in select permission
url: /v1alpha1/graphql
status: 200
headers:
X-Hasura-Role: user1
response:
data:
geom_table:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
query: |
query {
geom_table{
id
type
geom_col
}
}

View File

@ -0,0 +1,61 @@
description: User can query geometry values which satisfies filter in select permission using session variables
url: /v1alpha1/graphql
status: 200
headers:
X-Hasura-Role: user2
X-Hasura-Geom-Dist: '1'
X-Hasura-Geom-Val: '{"type":"Polygon","coordinates":[[[2,0],[2,1],[3,1],[3,0],[2,0]]]}'
response:
data:
geom_table:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
query: |
query {
geom_table{
id
type
geom_col
}
}

View File

@ -0,0 +1,20 @@
description: User can query geometry values which satisfies filter in select permission
url: /v1alpha1/graphql
status: 200
headers:
X-Hasura-Role: user1
response:
data:
jsonb_table:
- id: 1
jsonb_col:
name: Hasura
age: 7
query:
query: |
query {
jsonb_table{
id
jsonb_col
}
}

View File

@ -0,0 +1,21 @@
description: User can query geometry values which satisfies filter in select permission
url: /v1alpha1/graphql
status: 200
headers:
X-Hasura-Role: user2
X-Hasura-Has-Key: age
response:
data:
jsonb_table:
- id: 1
jsonb_col:
name: Hasura
age: 7
query:
query: |
query {
jsonb_table{
id
jsonb_col
}
}

View File

@ -0,0 +1,40 @@
description: Nested select on article
url: /v1/query
status: 200
response:
- content: Sample article content 2
author:
name: Author 1
id: 1
id: 2
title: Article 2
tags:
- bestseller
- latest
- content: Sample article content 3
author:
name: Author 2
id: 2
id: 3
title: Article 3
tags:
- latest
query:
type: select
args:
table: article
columns:
- id
- title
- content
- tags
- name: author
columns:
- id
- name
where:
tags:
$contained_in:
- bestseller
- latest

View File

@ -0,0 +1,32 @@
description: Nested select on article
url: /v1/query
status: 200
response:
- content: Sample article content 3
author:
name: Author 2
id: 2
id: 3
title: Article 3
tags:
- latest
query:
variables:
tags:
- latest
type: select
args:
table: article
where:
tags:
$contained_in:
- latest
columns:
- id
- title
- content
- tags
- name: author
columns:
- id
- name

View File

@ -0,0 +1,37 @@
description: Select author and their articles
url: /v1/query
status: 200
response:
- content: Sample article content 2
author:
name: Author 1
id: 1
id: 2
title: Article 2
tags:
- bestseller
- latest
- content: Sample article content 3
author:
name: Author 2
id: 2
id: 3
title: Article 3
tags:
- latest
query:
type: select
args:
table: article
where:
tags:
$contains: latest
columns:
- id
- title
- content
- tags
- name: author
columns:
- id
- name

View File

@ -0,0 +1,34 @@
description: Select author and their articles
url: /v1/query
status: 200
response:
- id: 1
name: Author 1
articles:
- id: 1
title: Article 1
content: Sample article content 1
tags:
- id: 2
title: Article 2
content: Sample article content 2
tags:
- bestseller
- latest
query:
type: select
args:
table: author
where:
articles:
tags:
$contains: bestseller
columns:
- id
- name
- name: articles
columns:
- id
- title
- content
- tags

View File

@ -0,0 +1,28 @@
description: Select products having key 'SIM type' in spec
url: /v1/query
status: 200
response:
- id: 3
category: Mobile
name: mobile1
spec:
Operating System Type: osType1
Weight: 200g
Processor: processor2
SIM type: DualSim
Sensors: Accelerometer sensor, E-compass, Proximity sensor
Network type: 4G
RAM: 3GB
Touchscreen: true
query:
type: select
args:
table: product
where:
spec:
$has_key: SIM type
columns:
- id
- category
- name
- spec

View File

@ -0,0 +1,40 @@
description: Select products having key 'SIM type' in spec
url: /v1/query
status: 200
response:
- id: 2
category: Laptop
name: laptop2
spec:
Disk: 128GB
Weight: 1.2Kg
OS: os2
Processor: processor2
RAM: 16GB
Touchscreen: true
- id: 3
category: Mobile
name: mobile1
spec:
Operating System Type: osType1
Weight: 200g
SIM type: DualSim
Sensors: Accelerometer sensor, E-compass, Proximity sensor
Network type: 4G
Processor: processor2
RAM: 3GB
Touchscreen: true
query:
type: select
args:
table: product
where:
spec:
$has_keys_all:
- Touchscreen
- RAM
columns:
- id
- category
- name
- spec

View File

@ -0,0 +1,37 @@
description: Select products having key 'SIM type' in spec
url: /v1/query
status: 200
response:
- id: 1
category: Laptop
name: laptop1
spec:
Disk: 128GB
Weight: 1.2Kg
Processor: processor1
Operating System: os1
RAM: 8GB
- id: 2
category: Laptop
name: laptop2
spec:
Disk: 128GB
Weight: 1.2Kg
Processor: processor2
OS: os2
RAM: 16GB
Touchscreen: true
query:
type: select
args:
table: product
where:
spec:
$has_keys_any:
- OS
- Operating System
columns:
- id
- category
- name
- spec

View File

@ -0,0 +1,144 @@
type: bulk
args:
#Author table
- type: run_sql
args:
sql: |
create table author(
id serial primary key,
name text unique
);
- type: track_table
args:
schema: public
name: author
#Article table
- type: run_sql
args:
sql: |
CREATE TABLE article (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
author_id INTEGER REFERENCES author(id),
is_published BOOLEAN,
published_on TIMESTAMP,
tags JSONB
)
- type: track_table
args:
schema: public
name: article
- type: run_sql
args:
sql: |
CREATE TABLE product (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
spec JSONB NOT NULL
);
- type: track_table
args:
schema: public
name: product
#Object relationship
- type: create_object_relationship
args:
table: article
name: author
using:
foreign_key_constraint_on: author_id
#Array relationship
- type: create_array_relationship
args:
table: author
name: articles
using:
foreign_key_constraint_on:
table: article
column: author_id
#Insert values
- type: run_sql
args:
sql: |
insert into author (name)
values
('Author 1'),
('Author 2')
- type: insert
args:
table: article
objects:
- title: Article 1
content: Sample article content 1
author_id: 1
- title: Article 2
content: Sample article content 2
author_id: 1
tags:
- bestseller
- latest
- title: Article 3
content: Sample article content 3
author_id: 2
tags:
- latest
- type: insert
args:
table: product
objects:
- category: Laptop
name: laptop1
spec:
Operating System: os1
RAM: 8GB
Disk: 128GB
Weight: 1.2Kg
Processor: processor1
- category: Laptop
name: laptop2
spec:
OS: os2
RAM: 16GB
Disk: 128GB
Weight: 1.2Kg
Processor: processor2
Touchscreen: yes
- category: Mobile
name: mobile1
spec:
Operating System Type: osType1
RAM: 3GB
Weight: 200g
Processor: processor2
Network type: 4G
SIM type: DualSim
Sensors: Accelerometer sensor, E-compass, Proximity sensor
Touchscreen: yes
- category: Electric kettle
name: kettle1
spec:
power: 1500W
capacity: 1.5L
dimensions:
width: 20cm
height: 19cm
depth: 18cm

View File

@ -0,0 +1,22 @@
type: bulk
args:
#Drop relationship first
- type: drop_relationship
args:
relationship: articles
table:
schema: public
name: author
- type: run_sql
args:
sql: |
drop table article
- type: run_sql
args:
sql: |
drop table author
- type: run_sql
args:
sql: |
drop table product

View File

@ -0,0 +1,65 @@
description: Query data from geom_table using postgis op $st_intersects in bool exp
url: /v1/query
status: 200
response:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
type: select
args:
table: geom_table
where:
$not:
geom_col:
$st_intersects:
type: LineString
coordinates:
- - -1
- 0
- - 0
- 1.5
- - 1
- 2
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,39 @@
description: Query data from geom_table using postgis op $st_contains in bool exp
url: /v1/query
status: 200
response:
- id: 1
type: point
geom_col:
type: Point
coordinates:
- 1
- 2
- id: 2
type: linestring
geom_col:
type: LineString
coordinates:
- - 0
- 0
- - 0.5
- 1
- - 1
- 2
- - 1.5
- 3
query:
type: select
args:
table: geom_table
where:
geom_col:
$st_contains:
type: Point
coordinates:
- 1
- 2
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,70 @@
description: Query data from geom_table using postgis op $st_d_within in bool exp
url: /v1/query
status: 200
response:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
type: select
args:
table: geom_table
where:
geom_col:
$st_d_within:
distance: 1
from:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,27 @@
description: Query data from geom_table using postgis op $st_equals in bool exp
url: /v1/query
status: 200
response:
- id: 1
type: point
geom_col:
type: Point
coordinates:
- 1
- 2
query:
type: select
args:
table: geom_table
where:
geom_col:
$st_equals:
type: Point
coordinates:
- 1
- 2
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,43 @@
description: Query data from geom_table using postgis op $st_touches in bool exp
url: /v1/query
status: 200
response:
- id: 1
type: point
geom_col:
type: Point
coordinates:
- 1
- 2
- id: 2
type: linestring
geom_col:
type: LineString
coordinates:
- - 0
- 0
- - 0.5
- 1
- - 1
- 2
- - 1.5
- 3
query:
type: select
args:
table: geom_table
where:
geom_col:
$st_touches:
type: LineString
coordinates:
- - -1
- 0
- - 0
- 1.5
- - 1
- 2
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,38 @@
type: bulk
args:
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis;
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis_topology;
#Create table
- type: run_sql
args:
sql: |
CREATE TABLE geom_table(
id SERIAL PRIMARY KEY,
type TEXT NOT NULL,
geom_col geometry NOT NULL
);
- type: track_table
args:
name: geom_table
schema: public
#Insert data
- type: run_sql
args:
sql: |
INSERT INTO geom_table (type, geom_col)
VALUES
('point', ST_GeomFromText('POINT(1 2)')),
('linestring', ST_GeomFromText('LINESTRING(0 0, 0.5 1, 1 2, 1.5 3)')),
('linestring', ST_GeomFromText('LINESTRING(1 0, 0.5 0.5, 0 1)')),
('polygon', ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')),
('polygon', ST_GeomFromText('POLYGON((2 0, 2 1, 3 1, 3 0, 2 0))'))
;

View File

@ -0,0 +1,6 @@
type: bulk
args:
- type: run_sql
args:
sql: |
DROP TABLE geom_table;

View File

@ -167,3 +167,133 @@ args:
content: Sample article content 4
author_id: 3
is_published: false
#Permission based on PostGIS operators
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis;
- type: run_sql
args:
sql: |
CREATE EXTENSION IF NOT EXISTS postgis_topology;
#Create table
- type: run_sql
args:
sql: |
CREATE TABLE geom_table(
id SERIAL PRIMARY KEY,
type TEXT NOT NULL,
geom_col geometry NOT NULL
);
- type: track_table
args:
name: geom_table
schema: public
#Create select permission using postgis operator
- type: create_select_permission
args:
table: geom_table
role: user1
permission:
columns:
- id
- type
- geom_col
filter:
geom_col:
$st_d_within:
distance: 1
from:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
#Create select permission using postgis operator and session variables
- type: create_select_permission
args:
table: geom_table
role: user2
permission:
columns:
- id
- type
- geom_col
filter:
geom_col:
$st_d_within:
distance: X-Hasura-Geom-Dist
from: X-Hasura-Geom-Val
#Insert data
- type: run_sql
args:
sql: |
INSERT INTO geom_table (type, geom_col)
VALUES
('point', ST_GeomFromText('POINT(1 2)')),
('linestring', ST_GeomFromText('LINESTRING(0 0, 0.5 1, 1 2, 1.5 3)')),
('linestring', ST_GeomFromText('LINESTRING(1 0, 0.5 0.5, 0 1)')),
('polygon', ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')),
('polygon', ST_GeomFromText('POLYGON((2 0, 2 1, 3 1, 3 0, 2 0))'))
;
#Permission based on JSONB operators
- type: run_sql
args:
sql: |
CREATE TABLE jsonb_table(
id SERIAL PRIMARY KEY,
jsonb_col jsonb NOT NULL
);
- type: track_table
args:
name: jsonb_table
schema: public
#Create select permission using jsonb operator
- type: create_select_permission
args:
table: jsonb_table
role: user1
permission:
columns:
- id
- jsonb_col
filter:
jsonb_col:
$has_key: age
- type: create_select_permission
args:
table: jsonb_table
role: user2
permission:
columns:
- id
- jsonb_col
filter:
jsonb_col:
$has_key: X-Hasura-Has-Key
#Insert data
- type: insert
args:
table: jsonb_table
objects:
- jsonb_col:
name: Hasura
age: 7
- jsonb_col:
name: Cross

View File

@ -1,14 +1,10 @@
type: bulk
args:
- type: run_sql
args:
sql: |
drop table article
cascade: true
- type: run_sql
args:
sql: |
drop table author
DROP TABLE article;
DROP TABLE author;
DROP TABLE geom_table;
DROP TABLE jsonb_table;
cascade: true

View File

@ -0,0 +1,55 @@
description: User can query geometry values which satisfies filter in select permission
url: /v1/query
status: 200
headers:
X-Hasura-Role: user1
response:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
type: select
args:
table: geom_table
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,57 @@
description: User can query geometry values which satisfies filter in select permission using session variables
url: /v1/query
status: 200
headers:
X-Hasura-Role: user2
X-Hasura-Geom-Dist: '1'
X-Hasura-Geom-Val: '{"type":"Polygon","coordinates":[[[2,0],[2,1],[3,1],[3,0],[2,0]]]}'
response:
- id: 3
type: linestring
geom_col:
type: LineString
coordinates:
- - 1
- 0
- - 0.5
- 0.5
- - 0
- 1
- id: 4
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 0
- 0
- - 0
- 1
- - 1
- 1
- - 1
- 0
- - 0
- 0
- id: 5
type: polygon
geom_col:
type: Polygon
coordinates:
- - - 2
- 0
- - 2
- 1
- - 3
- 1
- - 3
- 0
- - 2
- 0
query:
type: select
args:
table: geom_table
columns:
- id
- type
- geom_col

View File

@ -0,0 +1,17 @@
description: User can query geometry values which satisfies filter in select permission
url: /v1/query
status: 200
headers:
X-Hasura-Role: user1
response:
- id: 1
jsonb_col:
name: Hasura
age: 7
query:
type: select
args:
table: jsonb_table
columns:
- id
- jsonb_col

View File

@ -0,0 +1,18 @@
description: User can query geometry values which satisfies filter in select permission using session variables
url: /v1/query
status: 200
headers:
X-Hasura-Role: user2
X-Hasura-Has-Key: age
response:
- id: 1
jsonb_col:
name: Hasura
age: 7
query:
type: select
args:
table: jsonb_table
columns:
- id
- jsonb_col

View File

@ -200,6 +200,18 @@ class TestGraphqlQueryPermissions(DefaultTestSelectQueries):
def test_user_cannot_access_remarks_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml')
def test_user_can_query_geometry_values_filter(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_geometry_values_filter.yaml')
def test_user_can_query_geometry_values_filter_session_vars(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_geometry_values_filter_session_vars.yaml')
def test_user_can_query_jsonb_values_filter(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_jsonb_values_filter.yaml')
def test_user_can_query_jsonb_values_filter_session_vars(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_jsonb_values_filter_session_vars.yaml')
def test_artist_select_query_Track_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/artist_select_query_Track_fail.yaml')

View File

@ -158,6 +158,54 @@ class TestV1SelectBoolExpSearch(DefaultTestSelectQueries):
def dir(cls):
return 'queries/v1/select/boolexp/search'
class TestV1SelectBoolExpJSONB(DefaultTestSelectQueries):
def test_select_article_author_jsonb_contained_in_bestseller_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_bestseller_latest.yaml')
def test_select_article_author_jsonb_contained_in_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contained_in_latest.yaml')
def test_select_article_author_jsonb_contains_latest(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_article_author_jsonb_contains_latest.yaml')
def test_select_author_article_jsonb_contains_bestseller(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_author_article_jsonb_contains_bestseller.yaml')
# TODO:- Uncomment the following after adding has_keys_all and has_keys_any operators
# def test_select_product_jsonb_has_keys_all_ram_touchscreen(self, hge_ctx):
# check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_all_ram_touchscreen.yaml')
# def test_select_product_jsonb_has_keys_any_os_operating_system(self, hge_ctx):
# check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_keys_any_os_operating_system.yaml')
def test_select_product_jsonb_has_key_sim_type(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/select_product_jsonb_has_key_sim_type.yaml')
@classmethod
def dir(cls):
return 'queries/v1/select/boolexp/jsonb'
class TestV1SelectBoolExpPostGIS(DefaultTestSelectQueries):
def test_query_st_equals(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_st_equals.yaml')
def test_query_st_contains(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_st_contains.yaml')
def test_query_st_touches(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_st_touches.yaml')
def test_query_not_st_intersects(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_not_st_intersects.yaml')
def test_query_st_d_within(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_st_d_within.yaml')
@classmethod
def dir(cls):
return 'queries/v1/select/boolexp/postgis'
class TestV1SelectPermissions(DefaultTestSelectQueries):
@ -173,6 +221,18 @@ class TestV1SelectPermissions(DefaultTestSelectQueries):
def test_user_cannot_access_remarks_col(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml')
def test_user_can_query_geometry_values_filter(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_geometry_values_filter.yaml')
def test_user_can_query_geometry_values_filter_session_vars(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_geometry_values_filter_session_vars.yaml')
def test_user_can_query_jsonb_values_filter(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_jsonb_values_filter.yaml')
def test_user_can_query_jsonb_values_filter_session_vars(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/user_can_query_jsonb_values_filter_session_vars.yaml')
@classmethod
def dir(cls):
return 'queries/v1/select/permissions'