From 2971c268aa7151b7f743cae835fef9d9f8e85122 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 14:01:12 +0530 Subject: [PATCH 01/24] add _st_* operators to geography_comparison_exp --- server/src-lib/Hasura/GraphQL/Context.hs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 273692dc04e20..de9e6e3a9013a 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -197,7 +197,8 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinOpInpVal : map geomOpToInpVal geomOps) isGeometryTy + , bool [] (stDWithinOpInpVal : map geometryOpToInpVal geomOps) isGeometryTy + , bool [] (stDWithinOpInpVal : map geographyOpToInpVal geomOps) isGeographyTy , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType where @@ -262,8 +263,16 @@ mkCompExpInp colTy = PGGeometry -> True _ -> False - geomOpToInpVal (op, desc) = + isGeographyTy = case colTy of + PGGeography -> True + _ -> False + + geometryOpToInpVal (op, desc) = InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy PGGeometry + + geographyOpToInpVal (op, desc) = + InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy PGGeography + geomOps = [ ( "_st_contains" From ec3e27591264f4fc61da35fe078de59edfc53dd9 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 14:02:20 +0530 Subject: [PATCH 02/24] add geography boolexp to rql --- server/src-lib/Hasura/RQL/GBoolExp.hs | 41 ++++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/server/src-lib/Hasura/RQL/GBoolExp.hs b/server/src-lib/Hasura/RQL/GBoolExp.hs index cb1e3d2e192ae..762012ffde9ad 100644 --- a/server/src-lib/Hasura/RQL/GBoolExp.hs +++ b/server/src-lib/Hasura/RQL/GBoolExp.hs @@ -87,21 +87,21 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ -- "_has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal -- "$has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal - -- 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 + -- geometry and geography types + "_st_contains" -> parseGeometryOrGeographyOp ASTContains + "$st_contains" -> parseGeometryOrGeographyOp ASTContains + "_st_crosses" -> parseGeometryOrGeographyOp ASTCrosses + "$st_crosses" -> parseGeometryOrGeographyOp ASTCrosses + "_st_equals" -> parseGeometryOrGeographyOp ASTEquals + "$st_equals" -> parseGeometryOrGeographyOp ASTEquals + "_st_intersects" -> parseGeometryOrGeographyOp ASTIntersects + "$st_intersects" -> parseGeometryOrGeographyOp ASTIntersects + "_st_overlaps" -> parseGeometryOrGeographyOp ASTOverlaps + "$st_overlaps" -> parseGeometryOrGeographyOp ASTOverlaps + "_st_touches" -> parseGeometryOrGeographyOp ASTTouches + "$st_touches" -> parseGeometryOrGeographyOp ASTTouches + "_st_within" -> parseGeometryOrGeographyOp ASTWithin + "$st_within" -> parseGeometryOrGeographyOp ASTWithin "_st_d_within" -> parseSTDWithinObj "$st_d_within" -> parseSTDWithinObj @@ -158,8 +158,8 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ PGJSONB -> m ty -> throwError $ buildMsg ty [PGJSONB] - parseGeometryOp f = - geometryOnlyOp colTy >> f <$> parseOne + parseGeometryOrGeographyOp f = + geometryOrGeographyOp colTy >> f <$> parseOne parseSTDWithinObj = do WithinOp distVal fromVal <- parseVal @@ -178,9 +178,10 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ "incompatible column types : " <> cn <<> ", " <>> rhsCol else return rhsCol - geometryOnlyOp PGGeometry = return () - geometryOnlyOp ty = - throwError $ buildMsg ty [PGGeometry] + geometryOrGeographyOp PGGeometry = return () + geometryOrGeographyOp PGGeography = return () + geometryOrGeographyOp ty = + throwError $ buildMsg ty [PGGeometry, PGGeography] parseWithTy ty = parser ty val parseOne = parseWithTy colTy From d31d4481a53e39b0c36a7d19bb99708bce548a62 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 14:55:02 +0530 Subject: [PATCH 03/24] refactor to reduce loc --- server/src-lib/Hasura/GraphQL/Context.hs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index de9e6e3a9013a..7989c8d845ad9 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -197,8 +197,7 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinOpInpVal : map geometryOpToInpVal geomOps) isGeometryTy - , bool [] (stDWithinOpInpVal : map geographyOpToInpVal geomOps) isGeographyTy + , bool [] (stDWithinOpInpVal : map geoOpToInpVal geoOps) isGeometryOrGeographyTy , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType where @@ -259,21 +258,15 @@ mkCompExpInp colTy = stDWithinDesc = "is the column within a distance from a geometry value" - isGeometryTy = case colTy of - PGGeometry -> True - _ -> False - - isGeographyTy = case colTy of + isGeometryOrGeographyTy = case colTy of + PGGeometry -> True PGGeography -> True - _ -> False - - geometryOpToInpVal (op, desc) = - InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy PGGeometry + _ -> False - geographyOpToInpVal (op, desc) = - InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy PGGeography + geoOpToInpVal (op, desc) = + InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy colTy - geomOps = + geoOps = [ ( "_st_contains" , "does the column contain the given geometry value" From a259e27df188136748ac90ae1fe2d7426a0426f0 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 15:25:05 +0530 Subject: [PATCH 04/24] add geography support to console permission builder --- .../Services/Data/TablePermissions/PermissionBuilder/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js index 93bec09f96b22..5105ecf847dd3 100644 --- a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js +++ b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js @@ -22,7 +22,7 @@ export const PGTypes = { 'interval', ], json: ['json', 'jsonb'], - postgis: ['geometry'], + postgis: ['geometry', 'geography'], }; export const notBoolOperators = ['_not']; From e8b08b50b3dd65da5854d0fcb9d64903d0aaa93d Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 17:48:39 +0530 Subject: [PATCH 05/24] add st_d_within_geography_input type and update desc --- server/src-lib/Hasura/GraphQL/Context.hs | 54 +++++++++++++++++------- server/src-lib/Hasura/SQL/Types.hs | 6 +++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 7989c8d845ad9..ce57d9c9c7341 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -178,16 +178,25 @@ mkCompExpTy :: PGColType -> G.NamedType mkCompExpTy = G.NamedType . mkCompExpName +-- TODO(shahidhk) this should ideally be st_d_within_geometry {- input st_d_within_input { distance: Float! from: geometry! } -} - stDWithinInpTy :: G.NamedType stDWithinInpTy = G.NamedType "st_d_within_input" +{- +input st_d_within_geography_input { + distance: Float! + from: geography! +} +-} +stDWithinGeographyInpTy :: G.NamedType +stDWithinGeographyInpTy = G.NamedType "st_d_within_geography_input" + --- | make compare expression input type mkCompExpInp :: PGColType -> InpObjTyInfo @@ -197,7 +206,8 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinOpInpVal : map geoOpToInpVal geoOps) isGeometryOrGeographyTy + , bool [] (stDWithinOpInpVal : map geoOpToInpVal geoOps) isGeometryType + , bool [] (stDWithinGeographyOpInpVal : map geoOpToInpVal geoOps) isGeographyType , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType where @@ -253,41 +263,49 @@ mkCompExpInp colTy = ] -- Geometry related ops + isGeometryType = case colTy of + PGGeometry -> True + _ -> False stDWithinOpInpVal = InpValInfo (Just stDWithinDesc) "_st_d_within" Nothing $ G.toGT stDWithinInpTy stDWithinDesc = "is the column within a distance from a geometry value" - isGeometryOrGeographyTy = case colTy of - PGGeometry -> True + -- Geography related ops + isGeographyType = case colTy of PGGeography -> True _ -> False + stDWithinGeographyOpInpVal = + InpValInfo (Just stDWithinGeographyDesc) "_st_d_within_geography" Nothing $ G.toGT stDWithinGeographyInpTy + stDWithinGeographyDesc = + "is the column within a distance from a geography value" geoOpToInpVal (op, desc) = InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy colTy + colTyDesc = G.Description $ T.pack $ show colTy geoOps = [ ( "_st_contains" - , "does the column contain the given geometry value" + , "does the column contain the given " <> colTyDesc <> " value" ) , ( "_st_crosses" - , "does the column crosses the given geometry value" + , "does the column crosses the given " <> colTyDesc <> " value" ) , ( "_st_equals" - , "is the column equal to given geometry value. Directionality is ignored" + , "is the column equal to given " <> colTyDesc <> " value. Directionality is ignored" ) , ( "_st_intersects" - , "does the column spatially intersect the given geometry value" + , "does the column spatially intersect the given " <> colTyDesc <> " value" ) , ( "_st_overlaps" - , "does the column 'spatially overlap' (intersect but not completely contain) the given geometry value" + , "does the column 'spatially overlap' (intersect but not completely contain) the given " <> colTyDesc <> " value" ) , ( "_st_touches" - , "does the column have atleast one point in common with the given geometry value" + , "does the column have atleast one point in common with the given " <> colTyDesc <> " value" ) , ( "_st_within" - , "is the column contained in the given geometry value" + , "is the column contained in the given " <> colTyDesc <> " value" ) ] @@ -341,6 +359,7 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = , TIObj <$> subRootM , TIEnum <$> ordByEnumTyM , TIInpObj <$> stDWithinInpM + , TIInpObj <$> stDWithinGeographyInpM ] <> scalarTys <> compTys <> defaultTypes -- for now subscription root is query root @@ -369,10 +388,15 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = $ G.toGT $ G.toNT $ G.NamedType "String" ] - stDWithinInpM = bool Nothing (Just stDWithinInp) (PGGeometry `elem` colTys) - stDWithinInp = - mkHsraInpTyInfo Nothing stDWithinInpTy $ fromInpValL - [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy PGGeometry + -- _st_d_within has to stay with geometry type + stDWithinInpM = + bool Nothing (Just $ stDWithinInp stDWithinInpTy PGGeometry) (PGGeometry `elem` colTys) + -- _st_d_within_geography is created for geography type + stDWithinGeographyInpM = + bool Nothing (Just $ stDWithinInp stDWithinGeographyInpTy PGGeography) (PGGeography `elem` colTys) + stDWithinInp inpTy ty = + mkHsraInpTyInfo Nothing inpTy $ fromInpValL + [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy ty , InpValInfo Nothing "distance" Nothing $ G.toNT $ G.toNT $ mkScalarTy PGFloat ] diff --git a/server/src-lib/Hasura/SQL/Types.hs b/server/src-lib/Hasura/SQL/Types.hs index c04750b2957ec..e33a8afe5806f 100644 --- a/server/src-lib/Hasura/SQL/Types.hs +++ b/server/src-lib/Hasura/SQL/Types.hs @@ -404,3 +404,9 @@ isBigNum = \case PGNumeric -> True PGDouble -> True _ -> False + +isGeoType :: PGColType -> Bool +isGeoType = \case + PGGeometry -> True + PGGeography -> True + _ -> False From 5cc2a440f97aea4edce839e0b0dc754374fb46d5 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 19:53:47 +0530 Subject: [PATCH 06/24] rename the default stdwithin var to reflect geometry --- server/src-lib/Hasura/GraphQL/Context.hs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index ce57d9c9c7341..2830bc53bdc86 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -185,8 +185,8 @@ input st_d_within_input { from: geometry! } -} -stDWithinInpTy :: G.NamedType -stDWithinInpTy = G.NamedType "st_d_within_input" +stDWithinGeometryInpTy :: G.NamedType +stDWithinGeometryInpTy = G.NamedType "st_d_within_input" {- input st_d_within_geography_input { @@ -206,7 +206,7 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinOpInpVal : map geoOpToInpVal geoOps) isGeometryType + , bool [] (stDWithinGeometryOpInpVal : map geoOpToInpVal geoOps) isGeometryType , bool [] (stDWithinGeographyOpInpVal : map geoOpToInpVal geoOps) isGeographyType , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType @@ -266,9 +266,9 @@ mkCompExpInp colTy = isGeometryType = case colTy of PGGeometry -> True _ -> False - stDWithinOpInpVal = - InpValInfo (Just stDWithinDesc) "_st_d_within" Nothing $ G.toGT stDWithinInpTy - stDWithinDesc = + stDWithinGeometryOpInpVal = + InpValInfo (Just stDWithinGeometryDesc) "_st_d_within" Nothing $ G.toGT stDWithinGeometryInpTy + stDWithinGeometryDesc = "is the column within a distance from a geometry value" -- Geography related ops @@ -358,7 +358,7 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = , TIObj <$> mutRootM , TIObj <$> subRootM , TIEnum <$> ordByEnumTyM - , TIInpObj <$> stDWithinInpM + , TIInpObj <$> stDWithinGeometryInpM , TIInpObj <$> stDWithinGeographyInpM ] <> scalarTys <> compTys <> defaultTypes @@ -389,8 +389,8 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = ] -- _st_d_within has to stay with geometry type - stDWithinInpM = - bool Nothing (Just $ stDWithinInp stDWithinInpTy PGGeometry) (PGGeometry `elem` colTys) + stDWithinGeometryInpM = + bool Nothing (Just $ stDWithinInp stDWithinGeometryInpTy PGGeometry) (PGGeometry `elem` colTys) -- _st_d_within_geography is created for geography type stDWithinGeographyInpM = bool Nothing (Just $ stDWithinInp stDWithinGeographyInpTy PGGeography) (PGGeography `elem` colTys) From ae133ab34e63be772639708988896e0eeed4fb58 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 12 Mar 2019 20:19:12 +0530 Subject: [PATCH 07/24] add _st_d_within_geography support to the query --- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index b46f1f069282d..21b8cf5203ad7 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -55,15 +55,16 @@ parseOpExps annVal = do "_has_keys_any" -> fmap AHasKeysAny <$> parseMany asPGColText v "_has_keys_all" -> fmap AHasKeysAll <$> parseMany asPGColText v - -- geometry type related operators - "_st_contains" -> fmap ASTContains <$> asPGColValM v - "_st_crosses" -> fmap ASTCrosses <$> asPGColValM v - "_st_equals" -> fmap ASTEquals <$> asPGColValM v - "_st_intersects" -> fmap ASTIntersects <$> asPGColValM v - "_st_overlaps" -> fmap ASTOverlaps <$> asPGColValM v - "_st_touches" -> fmap ASTTouches <$> asPGColValM v - "_st_within" -> fmap ASTWithin <$> asPGColValM v - "_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj + -- geometry/geography type related operators + "_st_contains" -> fmap ASTContains <$> asPGColValM v + "_st_crosses" -> fmap ASTCrosses <$> asPGColValM v + "_st_equals" -> fmap ASTEquals <$> asPGColValM v + "_st_intersects" -> fmap ASTIntersects <$> asPGColValM v + "_st_overlaps" -> fmap ASTOverlaps <$> asPGColValM v + "_st_touches" -> fmap ASTTouches <$> asPGColValM v + "_st_within" -> fmap ASTWithin <$> asPGColValM v + "_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj + "_st_d_within_geography" -> asObjectM v >>= mapM parseAsSTDWithinObj _ -> throw500 @@ -82,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" + -- TODO(shahidhk): throw correct error message + throw500 "expected \"distance\" input field in st_d_within(geography)_input type" dist <- asPGColVal distanceVal fromVal <- onNothing (OMap.lookup "from" obj) $ - throw500 "expected \"from\" input field in st_d_within_input ty" + throw500 "expected \"from\" input field in st_d_within(geography)_input type" from <- asPGColVal fromVal return $ ASTDWithin $ WithinOp dist from From 39d512a174a85b3c494f00133f7296b04e74a1e4 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 12:19:50 +0530 Subject: [PATCH 08/24] add only st_d_within and st_intersects to geography types --- server/src-lib/Hasura/GraphQL/Context.hs | 47 +++++++++++-------- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 5 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 2830bc53bdc86..12493a685666d 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -206,8 +206,10 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinGeometryOpInpVal : map geoOpToInpVal geoOps) isGeometryType - , bool [] (stDWithinGeographyOpInpVal : map geoOpToInpVal geoOps) isGeographyType + , bool [] (stDWithinGeoOpInpVal stDWithinGeoDesc stDWithinGeometryInpTy : + map geoOpToInpVal (geoOps ++ geomOps)) isGeometryType + , bool [] (stDWithinGeoOpInpVal stDWithinGeoDesc stDWithinGeographyInpTy : + map geoOpToInpVal geoOps) isGeographyType , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType where @@ -262,50 +264,55 @@ mkCompExpInp colTy = ) ] + stDWithinGeoOpInpVal desc ty = + InpValInfo (Just desc) "_st_d_within" Nothing $ G.toGT ty + stDWithinGeoDesc = + "is the column within a distance from a " <> colTyDesc <> "value" + -- Geometry related ops isGeometryType = case colTy of PGGeometry -> True _ -> False - stDWithinGeometryOpInpVal = - InpValInfo (Just stDWithinGeometryDesc) "_st_d_within" Nothing $ G.toGT stDWithinGeometryInpTy - stDWithinGeometryDesc = - "is the column within a distance from a geometry value" -- Geography related ops isGeographyType = case colTy of PGGeography -> True _ -> False - stDWithinGeographyOpInpVal = - InpValInfo (Just stDWithinGeographyDesc) "_st_d_within_geography" Nothing $ G.toGT stDWithinGeographyInpTy - stDWithinGeographyDesc = - "is the column within a distance from a geography value" geoOpToInpVal (op, desc) = InpValInfo (Just desc) op Nothing $ G.toGT $ mkScalarTy colTy colTyDesc = G.Description $ T.pack $ show colTy - geoOps = + + -- operators applicable only to geometry types + geomOps :: [(G.Name, G.Description)] + geomOps = [ ( "_st_contains" - , "does the column contain the given " <> colTyDesc <> " value" + , "does the column contain the given geometry value" ) , ( "_st_crosses" - , "does the column crosses the given " <> colTyDesc <> " value" + , "does the column crosses the given geometry value" ) , ( "_st_equals" - , "is the column equal to given " <> colTyDesc <> " value. Directionality is ignored" - ) - , ( "_st_intersects" - , "does the column spatially intersect the given " <> colTyDesc <> " value" + , "is the column equal to given geometry value. Directionality is ignored" ) , ( "_st_overlaps" - , "does the column 'spatially overlap' (intersect but not completely contain) the given " <> colTyDesc <> " value" + , "does the column 'spatially overlap' (intersect but not completely contain) the given geometry value" ) , ( "_st_touches" - , "does the column have atleast one point in common with the given " <> colTyDesc <> " value" + , "does the column have atleast one point in common with the given geometry value" ) , ( "_st_within" - , "is the column contained in the given " <> colTyDesc <> " value" + , "is the column contained in the given geometry value" + ) + ] + + -- operators applicable to geometry and geography types + geoOps = + [ + ( "_st_intersects" + , "does the column spatially intersect the given " <> colTyDesc <> " value" ) ] diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index 21b8cf5203ad7..929ecb77d38d4 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -64,7 +64,6 @@ parseOpExps annVal = do "_st_touches" -> fmap ASTTouches <$> asPGColValM v "_st_within" -> fmap ASTWithin <$> asPGColValM v "_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj - "_st_d_within_geography" -> asObjectM v >>= mapM parseAsSTDWithinObj _ -> throw500 @@ -84,10 +83,10 @@ parseOpExps annVal = do parseAsSTDWithinObj obj = do distanceVal <- onNothing (OMap.lookup "distance" obj) $ -- TODO(shahidhk): throw correct error message - throw500 "expected \"distance\" input field in st_d_within(geography)_input type" + throw500 "expected \"distance\" input field in st_d_within" dist <- asPGColVal distanceVal fromVal <- onNothing (OMap.lookup "from" obj) $ - throw500 "expected \"from\" input field in st_d_within(geography)_input type" + throw500 "expected \"from\" input field in st_d_within" from <- asPGColVal fromVal return $ ASTDWithin $ WithinOp dist from From 551dcdf00458a8061c30dcb8db31e0dd1f28d2c8 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 12:20:29 +0530 Subject: [PATCH 09/24] fix a typo --- server/src-lib/Hasura/GraphQL/Context.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 12493a685666d..c984e3a8bcb9c 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -267,7 +267,7 @@ mkCompExpInp colTy = stDWithinGeoOpInpVal desc ty = InpValInfo (Just desc) "_st_d_within" Nothing $ G.toGT ty stDWithinGeoDesc = - "is the column within a distance from a " <> colTyDesc <> "value" + "is the column within a distance from a " <> colTyDesc <> " value" -- Geometry related ops isGeometryType = case colTy of From 5e507dd13aa868b05c962b2b9f76b0f5bf64695c Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 14:17:42 +0530 Subject: [PATCH 10/24] add tests for spatial operators on geography column --- .../postgis/query_geography_spatial_ops.yaml | 44 +++++++++++++++++++ .../graphql_query/boolexp/postgis/setup.yaml | 23 ++++++++++ .../boolexp/postgis/teardown.yaml | 4 ++ server/tests-py/test_graphql_queries.py | 3 ++ 4 files changed, 74 insertions(+) create mode 100644 server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml diff --git a/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml b/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml new file mode 100644 index 0000000000000..e5638df1503e1 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml @@ -0,0 +1,44 @@ +description: Test PostGIS spatial operators on a geography column +url: /v1alpha1/graphql +status: 200 +response: + data: + stdwithin: + - name: London + - name: Paris + stintersects: + - name: Linestring + - name: Point +query: + query: | + query geography($point: geography!, $ipoint: geography!) { + stdwithin: geog_table( + where: {geog_col: {_st_d_within: {distance: 1000000, from: $point}}} + ) { + name + } + stintersects: geog_table( + where: {geog_col: {_st_intersects: $ipoint}} + ) { + name + } + } + variables: + ipoint: + coordinates: + - -43.23456 + - 72.4567772 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + type: Point + point: + coordinates: + - 1 + - 50 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + type: Point diff --git a/server/tests-py/queries/graphql_query/boolexp/postgis/setup.yaml b/server/tests-py/queries/graphql_query/boolexp/postgis/setup.yaml index e57d8550f1ce7..6e19d87ab085f 100644 --- a/server/tests-py/queries/graphql_query/boolexp/postgis/setup.yaml +++ b/server/tests-py/queries/graphql_query/boolexp/postgis/setup.yaml @@ -23,6 +23,18 @@ args: args: name: geom_table schema: public +- type: run_sql + args: + sql: | + CREATE TABLE geog_table( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + geog_col geography NOT NULL + ); +- type: track_table + args: + name: geog_table + schema: public #Insert data - type: run_sql @@ -36,4 +48,15 @@ args: ('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))')) ; +- type: run_sql + args: + sql: | + INSERT INTO geog_table (name, geog_col) + VALUES + ('London', ST_GeographyFromText('POINT(0.1278 51.5074)') ), + ('Paris', ST_GeographyFromText('POINT(2.3522 48.8566)') ), + ('Moscow', ST_GeographyFromText('POINT(37.6173 55.7558)') ), + ('New York', ST_GeographyFromText('POINT(-74.0060 40.7128)') ), + ('Linestring', ST_GeographyFromText('SRID=4326;LINESTRING(-43.23456 72.4567,-43.23456 72.4568)')), + ('Point', ST_GeographyFromText('SRID=4326;POINT(-43.23456 72.4567772)')); diff --git a/server/tests-py/queries/graphql_query/boolexp/postgis/teardown.yaml b/server/tests-py/queries/graphql_query/boolexp/postgis/teardown.yaml index e440c971c6955..f79193038c2cc 100644 --- a/server/tests-py/queries/graphql_query/boolexp/postgis/teardown.yaml +++ b/server/tests-py/queries/graphql_query/boolexp/postgis/teardown.yaml @@ -4,3 +4,7 @@ args: args: sql: | DROP TABLE geom_table; +- type: run_sql + args: + sql: | + DROP TABLE geog_table; diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 64f7691c989f8..fbdafe5665359 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -295,6 +295,9 @@ def test_query_using_line(self, hge_ctx): def test_query_using_polygon(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/query_using_polygon.yaml') + def test_query_geography_spatial_ops(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_geography_spatial_ops.yaml') + @classmethod def dir(cls): return 'queries/graphql_query/boolexp/postgis' From 49504b98429f3088a57cc83e71929a2671a8170c Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 16:13:55 +0530 Subject: [PATCH 11/24] fix some indentation --- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index 929ecb77d38d4..1bcc3febed818 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -26,27 +26,27 @@ parseOpExps parseOpExps annVal = do opExpsM <- flip withObjectM annVal $ \nt objM -> forM objM $ \obj -> forM (OMap.toList obj) $ \(k, v) -> case k of - "_eq" -> fmap (AEQ True) <$> asPGColValM v - "_ne" -> fmap (ANE True) <$> asPGColValM v - "_neq" -> fmap (ANE True) <$> asPGColValM v - "_is_null" -> resolveIsNull v + "_eq" -> fmap (AEQ True) <$> asPGColValM v + "_ne" -> fmap (ANE True) <$> asPGColValM v + "_neq" -> fmap (ANE True) <$> asPGColValM v + "_is_null" -> resolveIsNull v - "_in" -> fmap (AIN . catMaybes) <$> parseMany asPGColValM v - "_nin" -> fmap (ANIN . catMaybes) <$> parseMany asPGColValM v + "_in" -> fmap (AIN . catMaybes) <$> parseMany asPGColValM v + "_nin" -> fmap (ANIN . catMaybes) <$> parseMany asPGColValM v - "_gt" -> fmap AGT <$> asPGColValM v - "_lt" -> fmap ALT <$> asPGColValM v - "_gte" -> fmap AGTE <$> asPGColValM v - "_lte" -> fmap ALTE <$> asPGColValM v + "_gt" -> fmap AGT <$> asPGColValM v + "_lt" -> fmap ALT <$> asPGColValM v + "_gte" -> fmap AGTE <$> asPGColValM v + "_lte" -> fmap ALTE <$> asPGColValM v - "_like" -> fmap ALIKE <$> asPGColValM v - "_nlike" -> fmap ANLIKE <$> asPGColValM v + "_like" -> fmap ALIKE <$> asPGColValM v + "_nlike" -> fmap ANLIKE <$> asPGColValM v - "_ilike" -> fmap AILIKE <$> asPGColValM v - "_nilike" -> fmap ANILIKE <$> asPGColValM v + "_ilike" -> fmap AILIKE <$> asPGColValM v + "_nilike" -> fmap ANILIKE <$> asPGColValM v - "_similar" -> fmap ASIMILAR <$> asPGColValM v - "_nsimilar" -> fmap ANSIMILAR <$> asPGColValM v + "_similar" -> fmap ASIMILAR <$> asPGColValM v + "_nsimilar" -> fmap ANSIMILAR <$> asPGColValM v -- jsonb related operators "_contains" -> fmap AContains <$> asPGColValM v @@ -56,14 +56,14 @@ parseOpExps annVal = do "_has_keys_all" -> fmap AHasKeysAll <$> parseMany asPGColText v -- geometry/geography type related operators - "_st_contains" -> fmap ASTContains <$> asPGColValM v - "_st_crosses" -> fmap ASTCrosses <$> asPGColValM v - "_st_equals" -> fmap ASTEquals <$> asPGColValM v - "_st_intersects" -> fmap ASTIntersects <$> asPGColValM v - "_st_overlaps" -> fmap ASTOverlaps <$> asPGColValM v - "_st_touches" -> fmap ASTTouches <$> asPGColValM v - "_st_within" -> fmap ASTWithin <$> asPGColValM v - "_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj + "_st_contains" -> fmap ASTContains <$> asPGColValM v + "_st_crosses" -> fmap ASTCrosses <$> asPGColValM v + "_st_equals" -> fmap ASTEquals <$> asPGColValM v + "_st_intersects" -> fmap ASTIntersects <$> asPGColValM v + "_st_overlaps" -> fmap ASTOverlaps <$> asPGColValM v + "_st_touches" -> fmap ASTTouches <$> asPGColValM v + "_st_within" -> fmap ASTWithin <$> asPGColValM v + "_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj _ -> throw500 From 0ed27ea3b3a556bd24fea680e2bcad2005206283 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 16:14:06 +0530 Subject: [PATCH 12/24] update docs --- docs/graphql/manual/queries/query-filters.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/graphql/manual/queries/query-filters.rst b/docs/graphql/manual/queries/query-filters.rst index e52936509ed17..31fbbfd1025db 100644 --- a/docs/graphql/manual/queries/query-filters.rst +++ b/docs/graphql/manual/queries/query-filters.rst @@ -588,9 +588,11 @@ PostGIS topology operators (_st_contains, _st_crosses, etc.) The ``_st_contains``, ``_st_crosses``, ``_st_equals``, ``_st_intersects``, ``_st_overlaps``, ``_st_touches``, ``_st_within`` and ``_st_d_within`` operators are used to filter based on ``geometry`` like columns. +``_st_d_within`` and ``_st_intersects`` can be used on ``geography`` columns also. + For more details on what these operators do, refer to `PostGIS docs `__. -Use JSON (`GeoJSON `__) representation of ``geometry`` values in +Use JSON (`GeoJSON `__) representation of ``geometry`` and ``geography`` values in ``variables`` as shown in the following examples: @@ -644,7 +646,7 @@ Fetch a list of geometry values which are within the given ``polygon`` value: Example: _st_d_within ^^^^^^^^^^^^^^^^^^^^^ -Fetch a list of geometry values which are 3 units from given ``point`` value: +Fetch a list of ``geometry`` values which are 3 units from given ``point`` value: .. graphiql:: :view_only: From f8280b29e106a5eb1818b961692096423c47fd01 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 13 Mar 2019 16:50:52 +0530 Subject: [PATCH 13/24] generate correct types in v1/query --- server/src-lib/Hasura/RQL/GBoolExp.hs | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/server/src-lib/Hasura/RQL/GBoolExp.hs b/server/src-lib/Hasura/RQL/GBoolExp.hs index 762012ffde9ad..dfb0bf52434bd 100644 --- a/server/src-lib/Hasura/RQL/GBoolExp.hs +++ b/server/src-lib/Hasura/RQL/GBoolExp.hs @@ -87,21 +87,22 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ -- "_has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal -- "$has_keys_all" -> jsonbOnlyOp $ AHasKeysAll <$> parseVal + -- 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_contains" -> parseGeometryOrGeographyOp ASTContains - "$st_contains" -> parseGeometryOrGeographyOp ASTContains - "_st_crosses" -> parseGeometryOrGeographyOp ASTCrosses - "$st_crosses" -> parseGeometryOrGeographyOp ASTCrosses - "_st_equals" -> parseGeometryOrGeographyOp ASTEquals - "$st_equals" -> parseGeometryOrGeographyOp ASTEquals "_st_intersects" -> parseGeometryOrGeographyOp ASTIntersects "$st_intersects" -> parseGeometryOrGeographyOp ASTIntersects - "_st_overlaps" -> parseGeometryOrGeographyOp ASTOverlaps - "$st_overlaps" -> parseGeometryOrGeographyOp ASTOverlaps - "_st_touches" -> parseGeometryOrGeographyOp ASTTouches - "$st_touches" -> parseGeometryOrGeographyOp ASTTouches - "_st_within" -> parseGeometryOrGeographyOp ASTWithin - "$st_within" -> parseGeometryOrGeographyOp ASTWithin "_st_d_within" -> parseSTDWithinObj "$st_d_within" -> parseSTDWithinObj @@ -158,6 +159,8 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ PGJSONB -> m ty -> throwError $ buildMsg ty [PGJSONB] + parseGeometryOp f = + geometryOp colTy >> f <$> parseOne parseGeometryOrGeographyOp f = geometryOrGeographyOp colTy >> f <$> parseOne @@ -178,6 +181,9 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ "incompatible column types : " <> cn <<> ", " <>> rhsCol else return rhsCol + geometryOp PGGeometry = return () + geometryOp ty = + throwError $ buildMsg ty [PGGeometry] geometryOrGeographyOp PGGeometry = return () geometryOrGeographyOp PGGeography = return () geometryOrGeographyOp ty = From 1533aeff27f33474ed31b8e613c5ac67d0ac4382 Mon Sep 17 00:00:00 2001 From: rikinsk Date: Thu, 14 Mar 2019 16:23:10 +0530 Subject: [PATCH 14/24] add permissions operators for geography type --- .../PermissionBuilder/PermissionBuilder.js | 15 ++++++++++----- .../TablePermissions/PermissionBuilder/utils.js | 17 +++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js index f02ad056b64cc..d0e5f534a0485 100644 --- a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js +++ b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js @@ -24,7 +24,8 @@ import { genericOperators, textColumnOperators, jsonColumnOperators, - topologyColumnOperators, + geometryColumnOperators, + geographyColumnOperators, PGTypes, } from './utils'; @@ -424,7 +425,8 @@ class PermissionBuilder extends React.Component { _val = Number(val); } else if ( (PGTypes.json.includes(valueType) || - PGTypes.postgis.includes(valueType)) && + PGTypes.geometry.includes(valueType) || + PGTypes.geography.includes(valueType)) && isJsonString(val) ) { _val = JSON.parse(val); @@ -453,7 +455,8 @@ class PermissionBuilder extends React.Component { input = renderBoolSelect(dispatchInput, value); } else if ( PGTypes.json.includes(valueType) || - PGTypes.postgis.includes(valueType) + PGTypes.geometry.includes(valueType) || + PGTypes.geography.includes(valueType) ) { input = inputBox(); suggestion = jsonSuggestion(); @@ -509,8 +512,10 @@ class PermissionBuilder extends React.Component { operators = textColumnOperators; } else if (PGTypes.json.includes(valueType)) { operators = jsonColumnOperators; - } else if (PGTypes.postgis.includes(valueType)) { - operators = topologyColumnOperators; + } else if (PGTypes.geometry.includes(valueType)) { + operators = geometryColumnOperators; + } else if (PGTypes.geography.includes(valueType)) { + operators = geographyColumnOperators; } else { operators = genericOperators; } diff --git a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js index 5105ecf847dd3..e17bc66649b12 100644 --- a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js +++ b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js @@ -22,7 +22,8 @@ export const PGTypes = { 'interval', ], json: ['json', 'jsonb'], - postgis: ['geometry', 'geography'], + geometry: ['geometry'], + geography: ['geography'], }; export const notBoolOperators = ['_not']; @@ -55,25 +56,29 @@ export const textOnlyColumnOperators = [ export const jsonColumnOperators = ['_contains', '_contained_in']; -export const topologyColumnOperators = [ +export const geometryOnlyColumnOperators = [ '_st_contains', '_st_crosses', '_st_equals', - '_st_intersects', '_st_overlaps', '_st_touches', '_st_within', - '_st_d_within', ]; +export const geographyColumnOperators = ['_st_d_within', '_st_intersects']; + export const boolOperators = notBoolOperators.concat(andOrBoolOperators); +export const geometryColumnOperators = geographyColumnOperators.concat( + geometryOnlyColumnOperators +); + export const columnOperators = genericSimpleColumnOperators .concat(genericArrayColumnOperators) .concat(genericBoolColumnOperators) .concat(textOnlyColumnOperators) .concat(jsonColumnOperators) - .concat(topologyColumnOperators); + .concat(geometryColumnOperators); export const genericOperators = genericSimpleColumnOperators .concat(genericArrayColumnOperators) @@ -104,7 +109,7 @@ export function isBoolTypeColumnOperator(value) { } export function isJsonTypeColumnOperator(value) { - return jsonColumnOperators.concat(topologyColumnOperators).includes(value); + return jsonColumnOperators.concat(geometryColumnOperators).includes(value); } export function isColumnOperator(value) { From 021248057cc00d5c7036e2bdfe2f36c4250de31e Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Mon, 18 Mar 2019 12:30:46 +0530 Subject: [PATCH 15/24] fix review comments --- server/src-lib/Hasura/GraphQL/Context.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index c984e3a8bcb9c..3536ed3dae342 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -206,9 +206,9 @@ mkCompExpInp colTy = , map (mk $ G.toLT colScalarTy) listOps , bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy , bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy - , bool [] (stDWithinGeoOpInpVal stDWithinGeoDesc stDWithinGeometryInpTy : + , bool [] (stDWithinGeoOpInpVal stDWithinGeometryInpTy : map geoOpToInpVal (geoOps ++ geomOps)) isGeometryType - , bool [] (stDWithinGeoOpInpVal stDWithinGeoDesc stDWithinGeographyInpTy : + , bool [] (stDWithinGeoOpInpVal stDWithinGeographyInpTy : map geoOpToInpVal geoOps) isGeographyType , [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"] ]) HasuraType @@ -264,8 +264,8 @@ mkCompExpInp colTy = ) ] - stDWithinGeoOpInpVal desc ty = - InpValInfo (Just desc) "_st_d_within" Nothing $ G.toGT ty + stDWithinGeoOpInpVal ty = + InpValInfo (Just stDWithinGeoDesc) "_st_d_within" Nothing $ G.toGT ty stDWithinGeoDesc = "is the column within a distance from a " <> colTyDesc <> " value" From 2839d4efb239e6ea50aa249d2daba52c2ac1447d Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 19 Mar 2019 18:34:47 +0530 Subject: [PATCH 16/24] wip: add use_spheroid to st_d_within_geography input --- server/src-lib/Hasura/GraphQL/Context.hs | 21 ++++++--- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 5 +- server/src-lib/Hasura/RQL/GBoolExp.hs | 36 +++++++++------ server/src-lib/Hasura/RQL/Types/BoolExp.hs | 46 +++++++++++++------ 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 3536ed3dae342..960b48ccd4d77 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -192,6 +192,7 @@ stDWithinGeometryInpTy = G.NamedType "st_d_within_input" input st_d_within_geography_input { distance: Float! from: geography! + use_spheroid: Bool! } -} stDWithinGeographyInpTy :: G.NamedType @@ -397,14 +398,22 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = -- _st_d_within has to stay with geometry type stDWithinGeometryInpM = - bool Nothing (Just $ stDWithinInp stDWithinGeometryInpTy PGGeometry) (PGGeometry `elem` colTys) + bool Nothing (Just $ stDWithinGeomInp) (PGGeometry `elem` colTys) -- _st_d_within_geography is created for geography type stDWithinGeographyInpM = - bool Nothing (Just $ stDWithinInp stDWithinGeographyInpTy PGGeography) (PGGeography `elem` colTys) - stDWithinInp inpTy ty = - mkHsraInpTyInfo Nothing inpTy $ fromInpValL - [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy ty - , InpValInfo Nothing "distance" Nothing $ G.toNT $ G.toNT $ mkScalarTy PGFloat + bool Nothing (Just $ stDWithinGeogInp) (PGGeography `elem` colTys) + + stDWithinGeomInp = + mkHsraInpTyInfo Nothing stDWithinGeometryInpTy $ fromInpValL + [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy PGGeometry + , InpValInfo Nothing "distance" Nothing $ G.toNT $ mkScalarTy PGFloat + ] + stDWithinGeogInp = + mkHsraInpTyInfo Nothing stDWithinGeographyInpTy $ fromInpValL + [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy PGGeography + , InpValInfo Nothing "distance" Nothing $ G.toNT $ mkScalarTy PGFloat + , InpValInfo + Nothing "use_spheroid" (Just $ G.VCBoolean True) $ G.toNT $ mkScalarTy PGBoolean ] emptyGCtx :: GCtx diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index 1bcc3febed818..fe72b43cf3c00 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -80,15 +80,16 @@ parseOpExps annVal = do AGScalar _ _ -> throw500 "boolean value is expected" _ -> tyMismatch "pgvalue" v + -- TODO(shahidhk): implement separate parser for geography parseAsSTDWithinObj obj = do distanceVal <- onNothing (OMap.lookup "distance" obj) $ - -- TODO(shahidhk): throw correct error message throw500 "expected \"distance\" input field in st_d_within" dist <- asPGColVal distanceVal fromVal <- onNothing (OMap.lookup "from" obj) $ throw500 "expected \"from\" input field in st_d_within" from <- asPGColVal fromVal - return $ ASTDWithin $ WithinOp dist from + return $ ASTDWithinGeom $ DWithinGeomOp dist from + parseAsEqOp :: (MonadError QErr m) diff --git a/server/src-lib/Hasura/RQL/GBoolExp.hs b/server/src-lib/Hasura/RQL/GBoolExp.hs index dfb0bf52434bd..8ec26253d6ae1 100644 --- a/server/src-lib/Hasura/RQL/GBoolExp.hs +++ b/server/src-lib/Hasura/RQL/GBoolExp.hs @@ -164,11 +164,19 @@ parseOpExp parser fim (PGColInfo cn colTy _) (opStr, val) = withErrPath $ parseGeometryOrGeographyOp f = geometryOrGeographyOp 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 + parseSTDWithinObj = case colTy of + PGGeometry -> do + DWithinGeomOp distVal fromVal <- parseVal + dist <- withPathK "distance" $ parser PGFloat distVal + from <- withPathK "from" $ parser colTy fromVal + return $ ASTDWithinGeom $ DWithinGeomOp dist from + PGGeography -> do + DWithinGeogOp distVal fromVal sphVal <- parseVal + dist <- withPathK "distance" $ parser PGFloat distVal + from <- withPathK "from" $ parser colTy fromVal + useSpheroid <- withPathK "use_spheroid" $ parser PGBoolean sphVal + return $ ASTDWithinGeog $ DWithinGeogOp dist from useSpheroid + _ -> throwError $ buildMsg colTy [PGGeometry, PGGeography] decodeAndValidateRhsCol = parseVal >>= validateRhsCol @@ -344,14 +352,16 @@ 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 - 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] + 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 + + ASTDWithinGeom (DWithinGeomOp r val) -> applySQLFn "ST_DWithin" [lhs, val, r] + ASTDWithinGeog (DWithinGeogOp r val sph) -> applySQLFn "ST_DWithin" [lhs, val, r, sph] ANISNULL -> S.BENull lhs ANISNOTNULL -> S.BENotNull lhs diff --git a/server/src-lib/Hasura/RQL/Types/BoolExp.hs b/server/src-lib/Hasura/RQL/Types/BoolExp.hs index 0befa97bf54af..6137976a05593 100644 --- a/server/src-lib/Hasura/RQL/Types/BoolExp.hs +++ b/server/src-lib/Hasura/RQL/Types/BoolExp.hs @@ -4,7 +4,8 @@ module Hasura.RQL.Types.BoolExp , gBoolExpToJSON , parseGBoolExp - , WithinOp(..) + , DWithinGeomOp(..) + , DWithinGeogOp(..) , OpExpG(..) , AnnBoolExpFld(..) @@ -93,12 +94,25 @@ foldBoolExp f (BoolNot notExp) = foldBoolExp f (BoolFld ce) = f ce -data WithinOp a = - WithinOp - { woDistance :: !a - , woFrom :: !a +data DWithinGeomOp a = + DWithinGeomOp + { dwgeomDistance :: !a + , dwgeomFrom :: !a } deriving (Show, Eq, Functor, Foldable, Traversable) -$(deriveJSON (aesonDrop 2 snakeCase) ''WithinOp) +$(deriveJSON (aesonDrop 6 snakeCase) ''DWithinGeomOp) + +data DWithinGeogOp a = + DWithinGeogOp + { dwgeogDistance :: !a + , dwgeogFrom :: !a + , dwgeogUseSpheroid :: !a + } deriving (Show, Eq, Functor, Foldable, Traversable) +$(deriveJSON (aesonDrop 6 snakeCase) ''DWithinGeogOp) + +data DWithinGeoOp a + = DWGOGeom !(DWithinGeomOp a) + | DWGOGeog !(DWithinGeogOp a) + deriving (Show, Eq, Functor, Foldable, Traversable) data OpExpG a = AEQ !Bool !a @@ -129,7 +143,8 @@ data OpExpG a | ASTContains !a | ASTCrosses !a - | ASTDWithin !(WithinOp a) + | ASTDWithinGeom !(DWithinGeomOp a) + | ASTDWithinGeog !(DWithinGeogOp a) | ASTEquals !a | ASTIntersects !a | ASTOverlaps !a @@ -176,14 +191,15 @@ opExpToJPair f = \case AHasKeysAny a -> ("_has_keys_any", toJSON a) AHasKeysAll a -> ("_has_keys_all", toJSON a) - ASTContains a -> ("_st_contains", f a) - ASTCrosses a -> ("_st_crosses", 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) - ASTTouches a -> ("_st_touches", f a) - ASTWithin a -> ("_st_within", f a) + ASTContains a -> ("_st_contains", f a) + ASTCrosses a -> ("_st_crosses", f a) + ASTDWithinGeom o -> ("_st_d_within", toJSON $ f <$> o) + ASTDWithinGeog 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) + ASTTouches a -> ("_st_touches", f a) + ASTWithin a -> ("_st_within", f a) ANISNULL -> ("_is_null", toJSON True) ANISNOTNULL -> ("_is_null", toJSON False) From d907b27c408beb9c915ece6c180bf2951495c5f9 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Tue, 19 Mar 2019 19:40:39 +0530 Subject: [PATCH 17/24] make use_spheroid optional --- server/src-lib/Hasura/GraphQL/Context.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index 960b48ccd4d77..6c958df7c9c5a 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -413,7 +413,7 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap = [ InpValInfo Nothing "from" Nothing $ G.toGT $ G.toNT $ mkScalarTy PGGeography , InpValInfo Nothing "distance" Nothing $ G.toNT $ mkScalarTy PGFloat , InpValInfo - Nothing "use_spheroid" (Just $ G.VCBoolean True) $ G.toNT $ mkScalarTy PGBoolean + Nothing "use_spheroid" (Just $ G.VCBoolean True) $ G.toGT $ mkScalarTy PGBoolean ] emptyGCtx :: GCtx From 6f0e810d94c27e09347e6256d2fa45b52a8bdea0 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Wed, 20 Mar 2019 16:25:07 +0530 Subject: [PATCH 18/24] parse use_speroid and its default value --- .../src-lib/Hasura/GraphQL/Resolve/BoolExp.hs | 28 ++++++++++++------- .../Hasura/GraphQL/Validate/InputValue.hs | 13 ++++++++- server/src-lib/Hasura/RQL/Types/BoolExp.hs | 5 ---- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs index 607f0ce020087..a1cd4f4677376 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs @@ -15,15 +15,17 @@ import Hasura.GraphQL.Resolve.InputValue import Hasura.GraphQL.Validate.Types import Hasura.RQL.Types import Hasura.SQL.Value +import Hasura.SQL.Types type OpExp = OpExpG AnnPGVal parseOpExps :: (MonadError QErr m) - => AnnInpVal -> m [OpExp] -parseOpExps annVal = do + => PGColType -> AnnInpVal -> m [OpExp] +parseOpExps colTy annVal = do opExpsM <- flip withObjectM annVal $ \nt objM -> forM objM $ \obj -> - forM (OMap.toList obj) $ \(k, v) -> case k of + forM (OMap.toList obj) $ \(k, v) -> do + case k of "_eq" -> fmap (AEQ True) <$> asPGColValM v "_ne" -> fmap (ANE True) <$> asPGColValM v "_neq" -> fmap (ANE True) <$> asPGColValM v @@ -78,16 +80,22 @@ parseOpExps annVal = do AGScalar _ _ -> throw500 "boolean value is expected" _ -> tyMismatch "pgvalue" v - -- TODO(shahidhk): implement separate parser for geography parseAsSTDWithinObj obj = do distanceVal <- onNothing (OMap.lookup "distance" obj) $ - throw500 "expected \"distance\" input field in st_d_within" + throw500 "expected \"distance\" input field in st_d_within" dist <- asPGColVal distanceVal fromVal <- onNothing (OMap.lookup "from" obj) $ - throw500 "expected \"from\" input field in st_d_within" + throw500 "expected \"from\" input field in st_d_within" from <- asPGColVal fromVal - return $ ASTDWithinGeom $ DWithinGeomOp dist from - + case colTy of + PGGeography -> do + useSpheroidVal <- onNothing (OMap.lookup "use_spheroid" obj) $ + throw500 "expected \"use_spheroid\" input field in st_d_within" + useSpheroid <- asPGColVal useSpheroidVal + return $ ASTDWithinGeog $ DWithinGeogOp dist from useSpheroid + PGGeometry -> do + return $ ASTDWithinGeom $ DWithinGeomOp dist from + _ -> throw500 "expected PGGeometry/PGGeography column for st_d_within" parseAsEqOp :: (MonadError QErr m) @@ -103,8 +111,8 @@ parseColExp parseColExp f nt n val = do fldInfo <- getFldInfo nt n case fldInfo of - Left pgColInfo -> do - opExps <- parseOpExps val + Left pgColInfo -> do + opExps <- parseOpExps (pgiType pgColInfo) val AVCol pgColInfo <$> traverse (traverse f) opExps Right (relInfo, _, permExp, _) -> do relBoolExp <- parseBoolExp f val diff --git a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs index f33357eab8bda..ac58f643aa636 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs @@ -209,11 +209,22 @@ validateObject valParser tyInfo flds = do "field " <> G.unName fldName <> " of type " <> G.showGT ty <> " is required, but not found" - fmap OMap.fromList $ forM flds $ \(fldName, fldVal) -> + -- get default values object + defValObj <- fmap (OMap.fromList . catMaybes) $ + forM (Map.toList $ _iotiFields tyInfo) $ + \(fldName, inpValInfo) -> do + let defValM = _iviDefVal inpValInfo + fldTy <- getInpFieldInfo tyInfo fldName + convDefValM <- + validateInputValue constValueParser fldTy `mapM` defValM + return $ (fldName,) <$> convDefValM + + inpObj <- fmap OMap.fromList $ forM flds $ \(fldName, fldVal) -> withPathK (G.unName fldName) $ do fldTy <- getInpFieldInfo tyInfo fldName convFldVal <- validateInputValue valParser fldTy fldVal return (fldName, convFldVal) + return $ inpObj `OMap.union` defValObj where inpFldNames = map fst flds diff --git a/server/src-lib/Hasura/RQL/Types/BoolExp.hs b/server/src-lib/Hasura/RQL/Types/BoolExp.hs index 6137976a05593..4bb34a4f4c751 100644 --- a/server/src-lib/Hasura/RQL/Types/BoolExp.hs +++ b/server/src-lib/Hasura/RQL/Types/BoolExp.hs @@ -109,11 +109,6 @@ data DWithinGeogOp a = } deriving (Show, Eq, Functor, Foldable, Traversable) $(deriveJSON (aesonDrop 6 snakeCase) ''DWithinGeogOp) -data DWithinGeoOp a - = DWGOGeom !(DWithinGeomOp a) - | DWGOGeog !(DWithinGeogOp a) - deriving (Show, Eq, Functor, Foldable, Traversable) - data OpExpG a = AEQ !Bool !a | ANE !Bool !a From 2b6b8fdfe247cb799e448f0f81b98328859195f9 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Thu, 21 Mar 2019 11:52:18 +0530 Subject: [PATCH 19/24] update tests --- .../boolexp/postgis/query_geography_spatial_ops.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml b/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml index e5638df1503e1..6f0035a36f365 100644 --- a/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml +++ b/server/tests-py/queries/graphql_query/boolexp/postgis/query_geography_spatial_ops.yaml @@ -6,6 +6,9 @@ response: stdwithin: - name: London - name: Paris + stdwithin_sph_false: + - name: London + - name: Paris stintersects: - name: Linestring - name: Point @@ -17,6 +20,11 @@ query: ) { name } + stdwithin_sph_false: geog_table( + where: {geog_col: {_st_d_within: {distance: 1000000, from: $point, use_spheroid: false}}} + ) { + name + } stintersects: geog_table( where: {geog_col: {_st_intersects: $ipoint}} ) { From 76f4666f95170767c14bb8ca3e4ea7dbda70fbdb Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Fri, 22 Mar 2019 11:29:52 +0530 Subject: [PATCH 20/24] add geometry and geography to reference types --- docs/graphql/manual/api-reference/pgtypes.csv | 2 + .../manual/api-reference/postgresql-types.rst | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/docs/graphql/manual/api-reference/pgtypes.csv b/docs/graphql/manual/api-reference/pgtypes.csv index a3ab027ab66ba..769cb68147983 100644 --- a/docs/graphql/manual/api-reference/pgtypes.csv +++ b/docs/graphql/manual/api-reference/pgtypes.csv @@ -19,6 +19,8 @@ json,,textual JSON data,JSON_ jsonb,,"binary JSON data, decomposed",JSONB_ line,,infinite line on a plane,Implicit_ lseg,,line segment on a plane, Implicit_ +geometry,,PostGIS Geometry type, Geometry_ +geography,,PostGIS Geography type, Geography_ macaddr,,MAC (Media Access Control) address, Implicit_ macaddr8,,MAC (Media Access Control) address (EUI-64 format), Implicit_ money,,currency amount,Implicit_ diff --git a/docs/graphql/manual/api-reference/postgresql-types.rst b/docs/graphql/manual/api-reference/postgresql-types.rst index 65c120b80e33d..2c54a014a0b8a 100644 --- a/docs/graphql/manual/api-reference/postgresql-types.rst +++ b/docs/graphql/manual/api-reference/postgresql-types.rst @@ -227,6 +227,79 @@ variable: } } +.. _Geometry: + +Geometry +-------- + +GraphQL custom scalar type ``geometry`` is generated for a ``GEOMETRY`` column +on a PostGIS enabled Postgres instance. Value should be given as GeoJSON. + +E.g. + +.. code-block:: graphql + + mutation insertGeometry($point: geometry!) { + insert_test( + objects: [{ + geometry_col: $point + }] + ) { + affected_rows + returning { + geometry_col + } + } + } + +varables: + +.. code-block:: json + + { + "point": { + "type": "Point", + "coordinates": [0, 0] + } + } + + +.. _Geography: + +Geography +-------- + +GraphQL custom scalar type ``geography`` is generated for a ``GEOGRAPHY`` column +on a PostGIS enabled Postgres instance. Value should be given as GeoJSON. + +E.g. + +.. code-block:: graphql + + mutation insertGeography($point: geography!) { + insert_test( + objects: [{ + geography_col: $point + }] + ) { + affected_rows + returning { + geography_col + } + } + } + +varables: + +.. code-block:: json + + { + "point": { + "type": "Point", + "coordinates": [0, 0] + } + } + .. _Implicit: Implicitly Supported types From ce6329317762bfd510f0b5c4550be74aa2e99942 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Fri, 22 Mar 2019 17:47:17 +0530 Subject: [PATCH 21/24] fix non-nullable fields with default value --- .../Hasura/GraphQL/Validate/InputValue.hs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs index ac58f643aa636..02599b691e98f 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs @@ -199,32 +199,31 @@ validateObject valParser tyInfo flds = do <> ", the following fields are duplicated: " <> showNames dups - -- check fields with not null types - forM_ (Map.toList $ _iotiFields tyInfo) $ + -- make default values object + defValObj <- fmap (OMap.fromList . catMaybes) $ + forM (Map.toList $ _iotiFields tyInfo) $ \(fldName, inpValInfo) -> do let ty = _iviType inpValInfo isNotNull = G.isNotNull ty + defValM = _iviDefVal inpValInfo + hasDefVal = isJust defValM fldPresent = fldName `elem` inpFldNames - when (not fldPresent && isNotNull) $ throwVE $ - "field " <> G.unName fldName <> " of type " <> G.showGT ty - <> " is required, but not found" - -- get default values object - defValObj <- fmap (OMap.fromList . catMaybes) $ - forM (Map.toList $ _iotiFields tyInfo) $ - \(fldName, inpValInfo) -> do - let defValM = _iviDefVal inpValInfo - fldTy <- getInpFieldInfo tyInfo fldName - convDefValM <- - validateInputValue constValueParser fldTy `mapM` defValM + when (not fldPresent && isNotNull && not hasDefVal) $ + throwVE $ "field " <> G.unName fldName <> " of type " + <> G.showGT ty <> " is required, but not found" + + convDefValM <- validateInputValue constValueParser ty `mapM` defValM return $ (fldName,) <$> convDefValM - inpObj <- fmap OMap.fromList $ forM flds $ \(fldName, fldVal) -> + -- compute input values object + inpValObj <- fmap OMap.fromList $ forM flds $ \(fldName, fldVal) -> withPathK (G.unName fldName) $ do fldTy <- getInpFieldInfo tyInfo fldName convFldVal <- validateInputValue valParser fldTy fldVal return (fldName, convFldVal) - return $ inpObj `OMap.union` defValObj + + return $ inpValObj `OMap.union` defValObj where inpFldNames = map fst flds From fcc431b0e2a6e1bc113284d82511810deb856336 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Mon, 25 Mar 2019 12:39:45 +0530 Subject: [PATCH 22/24] add tests for v1/query select with where on geography column --- .../boolexp/postgis/query_geog_stdwithin.yaml | 26 +++++++++++++++++++ .../postgis/query_geog_stdwithin_sph.yaml | 26 +++++++++++++++++++ .../postgis/query_geog_stintersects.yaml | 23 ++++++++++++++++ .../v1/select/boolexp/postgis/setup.yaml | 24 +++++++++++++++++ .../v1/select/boolexp/postgis/teardown.yaml | 4 +++ server/tests-py/test_v1_queries.py | 9 +++++++ 6 files changed, 112 insertions(+) create mode 100644 server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin.yaml create mode 100644 server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin_sph.yaml create mode 100644 server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stintersects.yaml diff --git a/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin.yaml b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin.yaml new file mode 100644 index 0000000000000..0ca8063232bd6 --- /dev/null +++ b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin.yaml @@ -0,0 +1,26 @@ +description: Query data from geog_table using st_d_within +url: /v1/query +status: 200 +response: +- name: London +- name: Paris +query: + type: select + args: + table: geog_table + where: + geog_col: + $st_d_within: + use_spheroid: true + distance: 1000000 + from: + coordinates: + - 1 + - 50 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + type: Point + columns: + - name diff --git a/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin_sph.yaml b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin_sph.yaml new file mode 100644 index 0000000000000..f1b16e1690738 --- /dev/null +++ b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stdwithin_sph.yaml @@ -0,0 +1,26 @@ +description: Query data from geog_table using st_d_within and use_spheroid false +url: /v1/query +status: 200 +response: +- name: London +- name: Paris +query: + type: select + args: + table: geog_table + where: + geog_col: + $st_d_within: + use_spheroid: false + distance: 1000000 + from: + coordinates: + - 1 + - 50 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + type: Point + columns: + - name diff --git a/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stintersects.yaml b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stintersects.yaml new file mode 100644 index 0000000000000..e62414de9cadf --- /dev/null +++ b/server/tests-py/queries/v1/select/boolexp/postgis/query_geog_stintersects.yaml @@ -0,0 +1,23 @@ +description: Query data from geog_table using st_intersects +url: /v1/query +status: 200 +response: +- name: Linestring +- name: Point +query: + type: select + args: + table: geog_table + where: + geog_col: + $st_intersects: + coordinates: + - -43.23456 + - 72.4567772 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + type: Point + columns: + - name diff --git a/server/tests-py/queries/v1/select/boolexp/postgis/setup.yaml b/server/tests-py/queries/v1/select/boolexp/postgis/setup.yaml index 60a534c4b79e1..7c74b7a10847c 100644 --- a/server/tests-py/queries/v1/select/boolexp/postgis/setup.yaml +++ b/server/tests-py/queries/v1/select/boolexp/postgis/setup.yaml @@ -24,6 +24,19 @@ args: name: geom_table schema: public +- type: run_sql + args: + sql: | + CREATE TABLE geog_table( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + geog_col geography NOT NULL + ); +- type: track_table + args: + name: geog_table + schema: public + #Insert data - type: run_sql args: @@ -36,3 +49,14 @@ args: ('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))')) ; +- type: run_sql + args: + sql: | + INSERT INTO geog_table (name, geog_col) + VALUES + ('London', ST_GeographyFromText('POINT(0.1278 51.5074)') ), + ('Paris', ST_GeographyFromText('POINT(2.3522 48.8566)') ), + ('Moscow', ST_GeographyFromText('POINT(37.6173 55.7558)') ), + ('New York', ST_GeographyFromText('POINT(-74.0060 40.7128)') ), + ('Linestring', ST_GeographyFromText('SRID=4326;LINESTRING(-43.23456 72.4567,-43.23456 72.4568)')), + ('Point', ST_GeographyFromText('SRID=4326;POINT(-43.23456 72.4567772)')); diff --git a/server/tests-py/queries/v1/select/boolexp/postgis/teardown.yaml b/server/tests-py/queries/v1/select/boolexp/postgis/teardown.yaml index e440c971c6955..f79193038c2cc 100644 --- a/server/tests-py/queries/v1/select/boolexp/postgis/teardown.yaml +++ b/server/tests-py/queries/v1/select/boolexp/postgis/teardown.yaml @@ -4,3 +4,7 @@ args: args: sql: | DROP TABLE geom_table; +- type: run_sql + args: + sql: | + DROP TABLE geog_table; diff --git a/server/tests-py/test_v1_queries.py b/server/tests-py/test_v1_queries.py index e2733c0e062c7..89950421c9d3a 100644 --- a/server/tests-py/test_v1_queries.py +++ b/server/tests-py/test_v1_queries.py @@ -203,6 +203,15 @@ def test_query_not_st_intersects(self, hge_ctx): def test_query_st_d_within(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/query_st_d_within.yaml') + def test_query_geog_stdwithin(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_geog_stdwithin.yaml') + + def test_query_geog_stdwithin_sph(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_geog_stdwithin_sph.yaml') + + def test_query_geog_stintersects(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/query_geog_stintersects.yaml') + @classmethod def dir(cls): return 'queries/v1/select/boolexp/postgis' From 21e45d42f7c7b799fd9df1b66087abd4a1b03285 Mon Sep 17 00:00:00 2001 From: Rikin Kachhia Date: Mon, 25 Mar 2019 14:33:25 +0530 Subject: [PATCH 23/24] Update postgresql-types.rst --- docs/graphql/manual/api-reference/postgresql-types.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/graphql/manual/api-reference/postgresql-types.rst b/docs/graphql/manual/api-reference/postgresql-types.rst index 2c54a014a0b8a..e48e09047f293 100644 --- a/docs/graphql/manual/api-reference/postgresql-types.rst +++ b/docs/graphql/manual/api-reference/postgresql-types.rst @@ -217,7 +217,7 @@ E.g. } } -variable: +variables: .. code-block:: json @@ -252,7 +252,7 @@ E.g. } } -varables: +variables: .. code-block:: json @@ -289,7 +289,7 @@ E.g. } } -varables: +variables: .. code-block:: json From 9bd5187def088a8f655b9356770f94c17fc0c287 Mon Sep 17 00:00:00 2001 From: Shahidh K Muhammed Date: Mon, 25 Mar 2019 15:04:12 +0530 Subject: [PATCH 24/24] add tests where geography ops are used in permissions --- server/src-lib/Hasura/EncJSON.hs | 2 +- .../queries/v1/select/permissions/setup.yaml | 64 +++++++++++++++++++ .../v1/select/permissions/teardown.yaml | 1 + .../user_can_query_geog_filter.yaml | 14 ++++ ...er_can_query_geog_filter_session_vars.yaml | 17 +++++ server/tests-py/test_v1_queries.py | 6 ++ 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter.yaml create mode 100644 server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter_session_vars.yaml diff --git a/server/src-lib/Hasura/EncJSON.hs b/server/src-lib/Hasura/EncJSON.hs index 0d8a10ca9beec..38e1c1eec20d9 100644 --- a/server/src-lib/Hasura/EncJSON.hs +++ b/server/src-lib/Hasura/EncJSON.hs @@ -3,8 +3,8 @@ module Hasura.EncJSON ( EncJSON - , encJToLBS , encJFromBuilder + , encJToLBS , encJFromJValue , encJFromChar , encJFromText diff --git a/server/tests-py/queries/v1/select/permissions/setup.yaml b/server/tests-py/queries/v1/select/permissions/setup.yaml index 48035d7a52cf1..c5739aca891f8 100644 --- a/server/tests-py/queries/v1/select/permissions/setup.yaml +++ b/server/tests-py/queries/v1/select/permissions/setup.yaml @@ -249,6 +249,70 @@ args: ('polygon', ST_GeomFromText('POLYGON((2 0, 2 1, 3 1, 3 0, 2 0))')) ; +#Permission based on Geography columns + +- type: run_sql + args: + sql: | + CREATE TABLE geog_table( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + geog_col geography NOT NULL + ); +- type: track_table + args: + name: geog_table + schema: public + +- type: create_select_permission + args: + table: geog_table + role: postgis_user1 + permission: + columns: + - id + - name + - geog_col + filter: + geog_col: + $st_intersects: + type: Point + coordinates: + - -43.23456 + - 72.4567772 + crs: + type: name + properties: + name: urn:ogc:def:crs:EPSG::4326 + +- type: create_select_permission + args: + table: geog_table + role: postgis_user2 + permission: + columns: + - id + - name + - geog_col + filter: + geog_col: + $st_d_within: + distance: X-Hasura-Geog-Dist + from: X-Hasura-Geog-From + use_spheroid: X-Hasura-Geog-Sph + +- type: run_sql + args: + sql: | + INSERT INTO geog_table (name, geog_col) + VALUES + ('London', ST_GeographyFromText('POINT(0.1278 51.5074)') ), + ('Paris', ST_GeographyFromText('POINT(2.3522 48.8566)') ), + ('Moscow', ST_GeographyFromText('POINT(37.6173 55.7558)') ), + ('New York', ST_GeographyFromText('POINT(-74.0060 40.7128)') ), + ('Linestring', ST_GeographyFromText('SRID=4326;LINESTRING(-43.23456 72.4567,-43.23456 72.4568)')), + ('Point', ST_GeographyFromText('SRID=4326;POINT(-43.23456 72.4567772)')); + #Permission based on JSONB operators - type: run_sql args: diff --git a/server/tests-py/queries/v1/select/permissions/teardown.yaml b/server/tests-py/queries/v1/select/permissions/teardown.yaml index 93746a56e787d..06dfc08062791 100644 --- a/server/tests-py/queries/v1/select/permissions/teardown.yaml +++ b/server/tests-py/queries/v1/select/permissions/teardown.yaml @@ -6,5 +6,6 @@ args: DROP TABLE article; DROP TABLE author; DROP TABLE geom_table; + DROP TABLE geog_table; DROP TABLE jsonb_table; cascade: true diff --git a/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter.yaml b/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter.yaml new file mode 100644 index 0000000000000..73695f1f8e912 --- /dev/null +++ b/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter.yaml @@ -0,0 +1,14 @@ +description: Query data from geog_table using st_intersects in permission +url: /v1/query +status: 200 +headers: + X-Hasura-Role: postgis_user1 +response: +- name: Linestring +- name: Point +query: + type: select + args: + table: geog_table + columns: + - name diff --git a/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter_session_vars.yaml b/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter_session_vars.yaml new file mode 100644 index 0000000000000..0caee0c3a6912 --- /dev/null +++ b/server/tests-py/queries/v1/select/permissions/user_can_query_geog_filter_session_vars.yaml @@ -0,0 +1,17 @@ +description: Query data from geog_table using st_d_within and session vars +url: /v1/query +status: 200 +headers: + X-Hasura-Role: postgis_user2 + X-Hasura-Geog-Sph: 'false' + X-Hasura-Geog-Dist: '1000000' + X-Hasura-Geog-From: '{"crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::4326"}}, "type": "Point", "coordinates": [1, 50]}' +response: +- name: London +- name: Paris +query: + type: select + args: + table: geog_table + columns: + - name diff --git a/server/tests-py/test_v1_queries.py b/server/tests-py/test_v1_queries.py index 89950421c9d3a..7150f079585c7 100644 --- a/server/tests-py/test_v1_queries.py +++ b/server/tests-py/test_v1_queries.py @@ -236,6 +236,12 @@ def test_user_can_query_geometry_values_filter(self, hge_ctx): 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_geog_filter(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/user_can_query_geog_filter.yaml') + + def test_user_can_query_geog_filter_session_vars(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/user_can_query_geog_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')