mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-19 19:29:29 +08:00
* initial raster support * _st_intersects_geom -> _st_intersects_geom_nband * add tests * update docs * improve docs As requested by @marionschleifer * new type for raster values Suggested by @lexi-lambda * replace `SEUnsafe "NULL"` with SENull
This commit is contained in:
committed by
Shahidh K Muhammed
parent
7f818b8b19
commit
f7c99689da
@@ -432,6 +432,40 @@ Operator
|
||||
field-name : {_st_d_within: {distance: Float, from: Value} }
|
||||
}
|
||||
|
||||
**Intersect Operators on RASTER columns:**
|
||||
|
||||
- ``_st_intersects_rast``
|
||||
|
||||
Executes ``boolean ST_Intersects( raster <raster-column> , raster <input-raster> )``
|
||||
|
||||
.. parsed-literal ::
|
||||
|
||||
{ _st_intersects_rast: raster }
|
||||
|
||||
|
||||
- ``_st_intersects_nband_geom``
|
||||
|
||||
Executes ``boolean ST_Intersects( raster <raster-column> , integer nband , geometry geommin )``
|
||||
|
||||
This accepts ``st_intersects_nband_geom_input`` input object
|
||||
|
||||
.. parsed-literal ::
|
||||
|
||||
{ _st_intersects_nband_geom: {nband: Integer! geommin: geometry!}
|
||||
|
||||
|
||||
|
||||
- ``_st_intersects_geom_nband``
|
||||
|
||||
Executes ``boolean ST_Intersects( raster <raster-column> , geometry geommin , integer nband = NULL )``
|
||||
|
||||
This accepts ``st_intersects_geom_nband_input`` input object
|
||||
|
||||
.. parsed-literal ::
|
||||
|
||||
{ _st_intersects_geom_nband: {geommin: geometry! nband: Integer }
|
||||
|
||||
|
||||
.. _CastExp:
|
||||
|
||||
CastExp
|
||||
|
||||
@@ -1508,3 +1508,157 @@ Columns of type ``geography`` are more accurate, but they don’t support as man
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE INDEX cities_location_geography ON cities USING GIST ((location::geography));
|
||||
|
||||
Intersect operators on RASTER columns
|
||||
-------------------------------------
|
||||
|
||||
Intersect operators on columns with ``raster`` type are supported.
|
||||
Refer to `Postgis docs <https://postgis.net/docs/RT_ST_Intersects.html>`__ to know more about intersect functions on ``raster`` columns.
|
||||
Please submit a feature request via `github <https://github.com/hasura/graphql-engine>`__ if you want support for more functions.
|
||||
|
||||
Example: _st_intersects_rast
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Filter the raster values which intersect the input raster value.
|
||||
|
||||
Executes the following SQL function:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
boolean ST_Intersects( raster <raster-col> , raster <raster-value> );
|
||||
|
||||
|
||||
.. graphiql::
|
||||
:view_only:
|
||||
:query:
|
||||
query getIntersectingValues ($rast: raster){
|
||||
dummy_rast(where: {rast: {_st_intersects_rast: $rast}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
:response:
|
||||
{
|
||||
"data": {
|
||||
"dummy_rast": [
|
||||
{
|
||||
"rid": 1,
|
||||
"rast": "01000001009A9999999999E93F9A9999999999E9BF000000000000F0BF000000000000104000000000000000000000000000000000E610000005000500440000010101000101010101010101010101010101010001010100"
|
||||
},
|
||||
{
|
||||
"rid": 2,
|
||||
"rast": "0100000100166C8E335B91F13FE2385B00285EF6BF360EE40064EBFFBF8D033900D9FA134000000000000000000000000000000000E610000005000500440000000101010001010101010101010101010101000101010000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
:variables:
|
||||
{
|
||||
"rast": "0100000100000000000000004000000000000000C00000000000000000000000000000084000000000000000000000000000000000E610000001000100440001"
|
||||
}
|
||||
|
||||
Example: _st_intersects_geom_nband
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Filter the raster values which intersect the input geometry value and optional band number.
|
||||
|
||||
Executes the following SQL function:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
boolean ST_Intersects( raster <raster-col> , geometry geommin , integer nband=NULL );
|
||||
|
||||
|
||||
.. graphiql::
|
||||
:view_only:
|
||||
:query:
|
||||
query getIntersectingValues ($point: geometry!){
|
||||
dummy_rast(where: {rast: {_st_intersects_geom_nband: {geommin: $point}}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
:response:
|
||||
{
|
||||
"data": {
|
||||
"dummy_rast": [
|
||||
{
|
||||
"rid": 1,
|
||||
"rast": "01000001009A9999999999E93F9A9999999999E9BF000000000000F0BF000000000000104000000000000000000000000000000000E610000005000500440000010101000101010101010101010101010101010001010100"
|
||||
},
|
||||
{
|
||||
"rid": 2,
|
||||
"rast": "0100000100166C8E335B91F13FE2385B00285EF6BF360EE40064EBFFBF8D033900D9FA134000000000000000000000000000000000E610000005000500440000000101010001010101010101010101010101000101010000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
:variables:
|
||||
{
|
||||
"point": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"crs": {
|
||||
"type": "name",
|
||||
"properties": {
|
||||
"name": "urn:ogc:def:crs:EPSG::4326"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Example: _st_intersects_nband_geom
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Filter the raster values (with specified band number) which intersect the input geometry value.
|
||||
|
||||
Executes the following SQL function:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
boolean ST_Intersects( raster <raster-col> , integer nband , geometry geommin );
|
||||
|
||||
|
||||
.. graphiql::
|
||||
:view_only:
|
||||
:query:
|
||||
query getIntersectingValues ($point: geometry!){
|
||||
dummy_rast(where: {rast: {_st_intersects_nband_geom: {nband: 5 geommin: $point}}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
:response:
|
||||
{
|
||||
"data": {
|
||||
"dummy_rast": [
|
||||
{
|
||||
"rid": 1,
|
||||
"rast": "01000001009A9999999999E93F9A9999999999E9BF000000000000F0BF000000000000104000000000000000000000000000000000E610000005000500440000010101000101010101010101010101010101010001010100"
|
||||
},
|
||||
{
|
||||
"rid": 2,
|
||||
"rast": "0100000100166C8E335B91F13FE2385B00285EF6BF360EE40064EBFFBF8D033900D9FA134000000000000000000000000000000000E610000005000500440000000101010001010101010101010101010101000101010000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
:variables:
|
||||
{
|
||||
"point": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"crs": {
|
||||
"type": "name",
|
||||
"properties": {
|
||||
"name": "urn:ogc:def:crs:EPSG::4326"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ library
|
||||
-- String related
|
||||
, case-insensitive
|
||||
, string-conversions
|
||||
, text-conversions
|
||||
|
||||
-- Http client
|
||||
, wreq
|
||||
|
||||
@@ -192,13 +192,15 @@ prepareWithPlan = \case
|
||||
_ -> getNextArgNum
|
||||
addPrepArg argNum $ toBinaryValue colVal
|
||||
return $ toPrepParam argNum (pstType colVal)
|
||||
|
||||
R.UVSessVar ty sessVar -> do
|
||||
let sessVarVal =
|
||||
S.SEOpApp (S.SQLOp "->>")
|
||||
[S.SEPrep 1, S.SELit $ T.toLower sessVar]
|
||||
return $ flip S.SETyAnn (S.mkTypeAnn ty) $ case ty of
|
||||
PGTypeScalar colTy -> withGeoVal colTy sessVarVal
|
||||
PGTypeScalar colTy -> withConstructorFn colTy sessVarVal
|
||||
PGTypeArray _ -> sessVarVal
|
||||
|
||||
R.UVSQL sqlExp -> return sqlExp
|
||||
|
||||
queryRootName :: Text
|
||||
|
||||
@@ -61,7 +61,7 @@ resolveVal userInfo = \case
|
||||
RS.UVSessVar ty sessVar -> do
|
||||
sessVarVal <- S.SELit <$> getSessVarVal userInfo sessVar
|
||||
return $ flip S.SETyAnn (S.mkTypeAnn ty) $ case ty of
|
||||
PGTypeScalar colTy -> withGeoVal colTy sessVarVal
|
||||
PGTypeScalar colTy -> withConstructorFn colTy sessVarVal
|
||||
PGTypeArray _ -> sessVarVal
|
||||
RS.UVSQL sqlExp -> return sqlExp
|
||||
|
||||
|
||||
@@ -67,7 +67,12 @@ parseOpExps colTy annVal = do
|
||||
"_st_overlaps" -> fmap ASTOverlaps <$> asOpRhs v
|
||||
"_st_touches" -> fmap ASTTouches <$> asOpRhs v
|
||||
"_st_within" -> fmap ASTWithin <$> asOpRhs v
|
||||
"_st_d_within" -> asObjectM v >>= mapM parseAsSTDWithinObj
|
||||
"_st_d_within" -> parseAsObjectM v parseAsSTDWithinObj
|
||||
|
||||
-- raster type related operators
|
||||
"_st_intersects_rast" -> fmap ASTIntersectsRast <$> asOpRhs v
|
||||
"_st_intersects_nband_geom" -> parseAsObjectM v parseAsSTIntersectsNbandGeomObj
|
||||
"_st_intersects_geom_nband" -> parseAsObjectM v parseAsSTIntersectsGeomNbandObj
|
||||
|
||||
_ ->
|
||||
throw500
|
||||
@@ -79,6 +84,8 @@ parseOpExps colTy annVal = do
|
||||
where
|
||||
asOpRhs = fmap (fmap UVPG) . asPGColumnValueM
|
||||
|
||||
parseAsObjectM v f = asObjectM v >>= mapM f
|
||||
|
||||
asPGArray rhsTy v = do
|
||||
valsM <- parseMany asPGColumnValue v
|
||||
forM valsM $ \vals -> do
|
||||
@@ -115,6 +122,23 @@ parseOpExps colTy annVal = do
|
||||
return $ ASTDWithinGeom $ DWithinGeomOp dist from
|
||||
_ -> throw500 "expected PGGeometry/PGGeography column for st_d_within"
|
||||
|
||||
parseAsSTIntersectsNbandGeomObj obj = do
|
||||
nbandVal <- onNothing (OMap.lookup "nband" obj) $
|
||||
throw500 "expected \"nband\" input field"
|
||||
nband <- UVPG <$> asPGColumnValue nbandVal
|
||||
geommin <- parseGeommin obj
|
||||
return $ ASTIntersectsNbandGeom $ STIntersectsNbandGeommin nband geommin
|
||||
|
||||
parseAsSTIntersectsGeomNbandObj obj = do
|
||||
nbandMM <- (fmap . fmap) UVPG <$> mapM asPGColumnValueM (OMap.lookup "nband" obj)
|
||||
geommin <- parseGeommin obj
|
||||
return $ ASTIntersectsGeomNband $ STIntersectsGeomminNband geommin $ join nbandMM
|
||||
|
||||
parseGeommin obj = do
|
||||
geomminVal <- onNothing (OMap.lookup "geommin" obj) $
|
||||
throw500 "expected \"geommin\" input field"
|
||||
UVPG <$> asPGColumnValue geomminVal
|
||||
|
||||
parseCastExpression
|
||||
:: (MonadError QErr m)
|
||||
=> AnnInpVal -> m (Maybe (CastExp UnresolvedVal))
|
||||
|
||||
@@ -60,7 +60,7 @@ convertRowObj val =
|
||||
flip withObject val $ \_ obj ->
|
||||
forM (OMap.toList obj) $ \(k, v) -> do
|
||||
prepExpM <- fmap UVPG <$> asPGColumnValueM v
|
||||
let prepExp = fromMaybe (UVSQL $ S.SEUnsafe "NULL") prepExpM
|
||||
let prepExp = fromMaybe (UVSQL S.SENull) prepExpM
|
||||
return (PGCol $ G.unName k, prepExp)
|
||||
|
||||
type ApplySQLOp = (PGCol, S.SQLExp) -> S.SQLExp
|
||||
|
||||
@@ -661,6 +661,7 @@ mkGCtx tyAgg (RootFields queryFields mutationFields) insCtxMap =
|
||||
, TIEnum <$> ordByEnumTyM
|
||||
] <>
|
||||
scalarTys <> compTys <> defaultTypes <> wiredInGeoInputTypes
|
||||
<> wiredInRastInputTypes
|
||||
-- for now subscription root is query root
|
||||
in GCtx allTys fldInfos queryRoot mutRootM subRootM ordByEnums
|
||||
(Map.map fst queryFields) (Map.map fst mutationFields) insCtxMap
|
||||
@@ -687,6 +688,17 @@ mkGCtx tyAgg (RootFields queryFields mutationFields) insCtxMap =
|
||||
-- operations even if just one of the two appears in the schema
|
||||
then Set.union (Set.fromList [PGColumnScalar PGGeometry, PGColumnScalar PGGeography]) colTys
|
||||
else colTys
|
||||
allScalarTypes = (allComparableTypes ^.. folded._PGColumnScalar) <> scalars
|
||||
|
||||
additionalScalars =
|
||||
Set.fromList
|
||||
-- raster comparison expression needs geometry input
|
||||
(guard anyRasterTypes *> pure PGGeometry)
|
||||
|
||||
allScalarTypes = (allComparableTypes ^.. folded._PGColumnScalar)
|
||||
<> additionalScalars <> scalars
|
||||
|
||||
wiredInGeoInputTypes = guard anyGeoTypes *> map TIInpObj geoInputTypes
|
||||
|
||||
anyRasterTypes = any (isScalarColumnWhere (== PGRaster)) colTys
|
||||
wiredInRastInputTypes = guard anyRasterTypes *>
|
||||
map TIInpObj rasterIntersectsInputTypes
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module Hasura.GraphQL.Schema.BoolExp
|
||||
( geoInputTypes
|
||||
, rasterIntersectsInputTypes
|
||||
, mkCompExpInp
|
||||
|
||||
, mkBoolExpTy
|
||||
@@ -67,16 +68,18 @@ mkCastExpressionInputType sourceType targetTypes =
|
||||
mkCompExpInp :: PGColumnType -> InpObjTyInfo
|
||||
mkCompExpInp colTy =
|
||||
InpObjTyInfo (Just tyDesc) (mkCompExpTy colTy) (fromInpValL $ concat
|
||||
[ map (mk colGqlType) typedOps
|
||||
[ map (mk colGqlType) eqOps
|
||||
, guard (isScalarWhere (/= PGRaster)) *> map (mk colGqlType) compOps
|
||||
, map (mk $ G.toLT $ G.toNT colGqlType) listOps
|
||||
, guard (isScalarWhere isStringType) *> map (mk $ mkScalarTy PGText) stringOps
|
||||
, guard (isScalarWhere (== PGJSONB)) *> map jsonbOpToInpVal jsonbOps
|
||||
, guard (isScalarWhere (== PGJSONB)) *> map opToInpVal jsonbOps
|
||||
, guard (isScalarWhere (== PGGeometry)) *>
|
||||
(stDWithinGeoOpInpVal stDWithinGeometryInpTy : map geoOpToInpVal (geoOps ++ geomOps))
|
||||
, guard (isScalarWhere (== PGGeography)) *>
|
||||
(stDWithinGeoOpInpVal stDWithinGeographyInpTy : map geoOpToInpVal geoOps)
|
||||
, [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"]
|
||||
, castOpInputValues
|
||||
, guard (isScalarWhere (== PGRaster)) *> map opToInpVal rasterOps
|
||||
]) TLHasuraType
|
||||
where
|
||||
colGqlType = mkColumnType colTy
|
||||
@@ -87,8 +90,12 @@ mkCompExpInp colTy =
|
||||
isScalarWhere = flip isScalarColumnWhere colTy
|
||||
mk t n = InpValInfo Nothing n Nothing $ G.toGT t
|
||||
|
||||
typedOps =
|
||||
["_eq", "_neq", "_gt", "_lt", "_gte", "_lte"]
|
||||
-- colScalarListTy = GA.GTList colGTy
|
||||
eqOps =
|
||||
["_eq", "_neq"]
|
||||
compOps =
|
||||
["_gt", "_lt", "_gte", "_lte"]
|
||||
|
||||
listOps =
|
||||
[ "_in", "_nin" ]
|
||||
-- TODO
|
||||
@@ -99,7 +106,8 @@ mkCompExpInp colTy =
|
||||
, "_similar", "_nsimilar"
|
||||
]
|
||||
|
||||
jsonbOpToInpVal (opName, ty, desc) = InpValInfo (Just desc) opName Nothing ty
|
||||
opToInpVal (opName, ty, desc) = InpValInfo (Just desc) opName Nothing ty
|
||||
|
||||
jsonbOps =
|
||||
[ ( "_contains"
|
||||
, G.toGT $ mkScalarTy PGJSONB
|
||||
@@ -168,6 +176,25 @@ mkCompExpInp colTy =
|
||||
)
|
||||
]
|
||||
|
||||
-- raster related operators
|
||||
rasterOps =
|
||||
[
|
||||
( "_st_intersects_rast"
|
||||
, G.toGT $ mkScalarTy PGRaster
|
||||
, boolFnMsg <> "ST_Intersects(raster <raster-col>, raster <raster-input>)"
|
||||
)
|
||||
, ( "_st_intersects_nband_geom"
|
||||
, G.toGT stIntersectsNbandGeomInputTy
|
||||
, boolFnMsg <> "ST_Intersects(raster <raster-col>, integer nband, geometry geommin)"
|
||||
)
|
||||
, ( "_st_intersects_geom_nband"
|
||||
, G.toGT stIntersectsGeomNbandInputTy
|
||||
, boolFnMsg <> "ST_Intersects(raster <raster-col> , geometry geommin, integer nband=NULL)"
|
||||
)
|
||||
]
|
||||
|
||||
boolFnMsg = "evaluates the following boolean Postgres function; "
|
||||
|
||||
geoInputTypes :: [InpObjTyInfo]
|
||||
geoInputTypes =
|
||||
[ stDWithinGeometryInputType
|
||||
@@ -189,6 +216,34 @@ geoInputTypes =
|
||||
Nothing "use_spheroid" (Just $ G.VCBoolean True) $ G.toGT $ mkScalarTy PGBoolean
|
||||
]
|
||||
|
||||
stIntersectsNbandGeomInputTy :: G.NamedType
|
||||
stIntersectsNbandGeomInputTy = G.NamedType "st_intersects_nband_geom_input"
|
||||
|
||||
stIntersectsGeomNbandInputTy :: G.NamedType
|
||||
stIntersectsGeomNbandInputTy = G.NamedType "st_intersects_geom_nband_input"
|
||||
|
||||
rasterIntersectsInputTypes :: [InpObjTyInfo]
|
||||
rasterIntersectsInputTypes =
|
||||
[ stIntersectsNbandGeomInput
|
||||
, stIntersectsGeomNbandInput
|
||||
]
|
||||
where
|
||||
stIntersectsNbandGeomInput =
|
||||
mkHsraInpTyInfo Nothing stIntersectsNbandGeomInputTy $ fromInpValL
|
||||
[ InpValInfo Nothing "nband" Nothing $
|
||||
G.toGT $ G.toNT $ mkScalarTy PGInteger
|
||||
, InpValInfo Nothing "geommin" Nothing $
|
||||
G.toGT $ G.toNT $ mkScalarTy PGGeometry
|
||||
]
|
||||
|
||||
stIntersectsGeomNbandInput =
|
||||
mkHsraInpTyInfo Nothing stIntersectsGeomNbandInputTy $ fromInpValL
|
||||
[ InpValInfo Nothing "geommin" Nothing $
|
||||
G.toGT $ G.toNT $ mkScalarTy PGGeometry
|
||||
, InpValInfo Nothing "nband" Nothing $
|
||||
G.toGT $ mkScalarTy PGInteger
|
||||
]
|
||||
|
||||
mkBoolExpName :: QualifiedTable -> G.Name
|
||||
mkBoolExpName tn =
|
||||
qualObjectToName tn <> "_bool_exp"
|
||||
|
||||
@@ -80,16 +80,16 @@ getTriggerSql op trn qt allCols strfyNum spec =
|
||||
]
|
||||
renderOldDataExp op2 scs =
|
||||
case op2 of
|
||||
INSERT -> S.SEUnsafe "NULL"
|
||||
INSERT -> S.SENull
|
||||
UPDATE -> getRowExpression OLD scs
|
||||
DELETE -> getRowExpression OLD scs
|
||||
MANUAL -> S.SEUnsafe "NULL"
|
||||
MANUAL -> S.SENull
|
||||
renderNewDataExp op2 scs =
|
||||
case op2 of
|
||||
INSERT -> getRowExpression NEW scs
|
||||
UPDATE -> getRowExpression NEW scs
|
||||
DELETE -> S.SEUnsafe "NULL"
|
||||
MANUAL -> S.SEUnsafe "NULL"
|
||||
DELETE -> S.SENull
|
||||
MANUAL -> S.SENull
|
||||
getRowExpression opVar scs =
|
||||
case scs of
|
||||
SubCStar -> applyRowToJson $ S.SEUnsafe $ opToTxt opVar
|
||||
|
||||
@@ -223,7 +223,7 @@ sessVarFromCurrentSetting' :: PGType PGScalarType -> SessVar -> S.SQLExp
|
||||
sessVarFromCurrentSetting' ty sessVar =
|
||||
flip S.SETyAnn (S.mkTypeAnn ty) $
|
||||
case ty of
|
||||
PGTypeScalar baseTy -> withGeoVal baseTy sessVarVal
|
||||
PGTypeScalar baseTy -> withConstructorFn baseTy sessVarVal
|
||||
PGTypeArray _ -> sessVarVal
|
||||
where
|
||||
curSess = S.SEUnsafe "current_setting('hasura.user')::json"
|
||||
|
||||
@@ -381,6 +381,13 @@ mkColCompExp qual lhsCol = mkCompExp (mkQCol lhsCol)
|
||||
ASTDWithinGeog (DWithinGeogOp r val sph) ->
|
||||
applySQLFn "ST_DWithin" [lhs, val, r, sph]
|
||||
|
||||
ASTIntersectsRast val ->
|
||||
applySTIntersects [lhs, val]
|
||||
ASTIntersectsNbandGeom (STIntersectsNbandGeommin nband geommin) ->
|
||||
applySTIntersects [lhs, nband, geommin]
|
||||
ASTIntersectsGeomNband (STIntersectsGeomminNband geommin mNband)->
|
||||
applySTIntersects [lhs, geommin, withSQLNull mNband]
|
||||
|
||||
ANISNULL -> S.BENull lhs
|
||||
ANISNOTNULL -> S.BENotNull lhs
|
||||
CEQ rhsCol -> S.BECompare S.SEQ lhs $ mkQCol rhsCol
|
||||
@@ -394,6 +401,10 @@ mkColCompExp qual lhsCol = mkCompExp (mkQCol lhsCol)
|
||||
|
||||
applySQLFn f exps = S.BEExp $ S.SEFnApp f exps Nothing
|
||||
|
||||
applySTIntersects = applySQLFn "ST_Intersects"
|
||||
|
||||
withSQLNull = fromMaybe S.SENull
|
||||
|
||||
mkCastsExp casts =
|
||||
sqlAll . flip map (M.toList casts) $ \(targetType, operations) ->
|
||||
let targetAnn = S.mkTypeAnn $ PGTypeScalar targetType
|
||||
|
||||
@@ -9,6 +9,8 @@ module Hasura.RQL.Types.BoolExp
|
||||
, CastExp
|
||||
, OpExpG(..)
|
||||
, opExpDepCol
|
||||
, STIntersectsNbandGeommin(..)
|
||||
, STIntersectsGeomminNband(..)
|
||||
|
||||
, AnnBoolExpFld(..)
|
||||
, AnnBoolExp
|
||||
@@ -123,6 +125,20 @@ data DWithinGeogOp a =
|
||||
} deriving (Show, Eq, Functor, Foldable, Traversable, Data)
|
||||
$(deriveJSON (aesonDrop 6 snakeCase) ''DWithinGeogOp)
|
||||
|
||||
data STIntersectsNbandGeommin a =
|
||||
STIntersectsNbandGeommin
|
||||
{ singNband :: !a
|
||||
, singGeommin :: !a
|
||||
} deriving (Show, Eq, Functor, Foldable, Traversable, Data)
|
||||
$(deriveJSON (aesonDrop 4 snakeCase) ''STIntersectsNbandGeommin)
|
||||
|
||||
data STIntersectsGeomminNband a =
|
||||
STIntersectsGeomminNband
|
||||
{ signGeommin :: !a
|
||||
, signNband :: !(Maybe a)
|
||||
} deriving (Show, Eq, Functor, Foldable, Traversable, Data)
|
||||
$(deriveJSON (aesonDrop 4 snakeCase) ''STIntersectsGeomminNband)
|
||||
|
||||
type CastExp a = M.HashMap PGScalarType [OpExpG a]
|
||||
|
||||
data OpExpG a
|
||||
@@ -164,6 +180,10 @@ data OpExpG a
|
||||
| ASTTouches !a
|
||||
| ASTWithin !a
|
||||
|
||||
| ASTIntersectsRast !a
|
||||
| ASTIntersectsGeomNband !(STIntersectsGeomminNband a)
|
||||
| ASTIntersectsNbandGeom !(STIntersectsNbandGeommin a)
|
||||
|
||||
| ANISNULL -- IS NULL
|
||||
| ANISNOTNULL -- IS NOT NULL
|
||||
|
||||
@@ -225,6 +245,10 @@ opExpToJPair f = \case
|
||||
ASTTouches a -> ("_st_touches", f a)
|
||||
ASTWithin a -> ("_st_within", f a)
|
||||
|
||||
ASTIntersectsRast a -> ("_st_intersects_rast", f a)
|
||||
ASTIntersectsNbandGeom a -> ("_st_intersects_nband_geom", toJSON $ f <$> a)
|
||||
ASTIntersectsGeomNband a -> ("_st_intersects_geom_nband", toJSON $ f <$> a)
|
||||
|
||||
ANISNULL -> ("_is_null", toJSON True)
|
||||
ANISNOTNULL -> ("_is_null", toJSON False)
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ instance ToSQL SQLExp where
|
||||
toSQL (SEPrep argNumber) =
|
||||
TB.char '$' <> fromString (show argNumber)
|
||||
toSQL SENull =
|
||||
TB.text "null"
|
||||
TB.text "NULL"
|
||||
toSQL (SELit tv) =
|
||||
TB.text $ pgFmtLit tv
|
||||
toSQL (SEUnsafe t) =
|
||||
|
||||
@@ -267,6 +267,7 @@ data PGScalarType
|
||||
| PGJSONB
|
||||
| PGGeometry
|
||||
| PGGeography
|
||||
| PGRaster
|
||||
| PGUnknown !T.Text
|
||||
deriving (Show, Eq, Lift, Generic, Data)
|
||||
|
||||
@@ -293,6 +294,7 @@ instance ToSQL PGScalarType where
|
||||
PGJSONB -> "jsonb"
|
||||
PGGeometry -> "geometry"
|
||||
PGGeography -> "geography"
|
||||
PGRaster -> "raster"
|
||||
PGUnknown t -> TB.text t
|
||||
|
||||
instance ToJSON PGScalarType where
|
||||
@@ -351,6 +353,8 @@ txtToPgColTy t = case t of
|
||||
|
||||
"geometry" -> PGGeometry
|
||||
"geography" -> PGGeography
|
||||
|
||||
"raster" -> PGRaster
|
||||
_ -> PGUnknown t
|
||||
|
||||
|
||||
@@ -379,6 +383,8 @@ pgTypeOid PGJSONB = PTI.jsonb
|
||||
-- we are using the ST_GeomFromGeoJSON($i) instead of $i
|
||||
pgTypeOid PGGeometry = PTI.text
|
||||
pgTypeOid PGGeography = PTI.text
|
||||
-- we are using the ST_RastFromHexWKB($i) instead of $i
|
||||
pgTypeOid PGRaster = PTI.text
|
||||
pgTypeOid (PGUnknown _) = PTI.auto
|
||||
|
||||
isIntegerType :: PGScalarType -> Bool
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Hasura.SQL.Value
|
||||
( PGScalarValue(..)
|
||||
, pgColValueToInt
|
||||
, withGeoVal
|
||||
, withConstructorFn
|
||||
, parsePGValue
|
||||
|
||||
, TxtEncodedPGVal
|
||||
@@ -30,13 +30,27 @@ import Hasura.Prelude
|
||||
|
||||
import qualified Data.Aeson.Text as AE
|
||||
import qualified Data.Aeson.Types as AT
|
||||
import qualified Data.ByteString as B
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Conversions as TC
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import qualified Data.Text.Lazy as TL
|
||||
|
||||
import qualified Database.PostgreSQL.LibPQ as PQ
|
||||
import qualified PostgreSQL.Binary.Encoding as PE
|
||||
|
||||
newtype RasterWKB
|
||||
= RasterWKB { getRasterWKB :: TC.Base16 B.ByteString }
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance FromJSON RasterWKB where
|
||||
parseJSON = \case
|
||||
String t -> case TC.fromText t of
|
||||
Just v -> return $ RasterWKB v
|
||||
Nothing -> fail
|
||||
"invalid hexadecimal representation of raster well known binary format"
|
||||
_ -> fail "expecting String for raster"
|
||||
|
||||
-- Binary value. Used in prepared sq
|
||||
data PGScalarValue
|
||||
= PGValInteger !Int32
|
||||
@@ -56,6 +70,7 @@ data PGScalarValue
|
||||
| PGValJSON !Q.JSON
|
||||
| PGValJSONB !Q.JSONB
|
||||
| PGValGeo !GeometryWithCRS
|
||||
| PGValRaster !RasterWKB
|
||||
| PGValUnknown !T.Text
|
||||
deriving (Show, Eq)
|
||||
|
||||
@@ -65,15 +80,17 @@ pgColValueToInt (PGValSmallInt i) = Just $ fromIntegral i
|
||||
pgColValueToInt (PGValBigInt i) = Just $ fromIntegral i
|
||||
pgColValueToInt _ = Nothing
|
||||
|
||||
withGeoVal :: PGScalarType -> S.SQLExp -> S.SQLExp
|
||||
withGeoVal ty v
|
||||
withConstructorFn :: PGScalarType -> S.SQLExp -> S.SQLExp
|
||||
withConstructorFn ty v
|
||||
| isGeoType ty = S.SEFnApp "ST_GeomFromGeoJSON" [v] Nothing
|
||||
| ty == PGRaster = S.SEFnApp "ST_RastFromHexWKB" [v] Nothing
|
||||
| otherwise = v
|
||||
|
||||
parsePGValue :: PGScalarType -> Value -> AT.Parser PGScalarValue
|
||||
parsePGValue ty val = case (ty, val) of
|
||||
(_ , Null) -> pure $ PGNull ty
|
||||
(PGUnknown _, String t) -> pure $ PGValUnknown t
|
||||
(PGRaster , _) -> parseTyped -- strictly parse raster value
|
||||
(_ , String t) -> parseTyped <|> pure (PGValUnknown t)
|
||||
(_ , _) -> parseTyped
|
||||
where
|
||||
@@ -97,7 +114,9 @@ parsePGValue ty val = case (ty, val) of
|
||||
PGJSONB -> PGValJSONB . Q.JSONB <$> parseJSON val
|
||||
PGGeometry -> PGValGeo <$> parseJSON val
|
||||
PGGeography -> PGValGeo <$> parseJSON val
|
||||
PGUnknown tyName -> fail $ "A string is expected for type : " ++ T.unpack tyName
|
||||
PGRaster -> PGValRaster <$> parseJSON val
|
||||
PGUnknown tyName ->
|
||||
fail $ "A string is expected for type : " ++ T.unpack tyName
|
||||
|
||||
data TxtEncodedPGVal
|
||||
= TENull
|
||||
@@ -136,6 +155,7 @@ txtEncodedPGVal colVal = case colVal of
|
||||
AE.encodeToLazyText j
|
||||
PGValGeo o -> TELit $ TL.toStrict $
|
||||
AE.encodeToLazyText o
|
||||
PGValRaster r -> TELit $ TC.toText $ getRasterWKB r
|
||||
PGValUnknown t -> TELit t
|
||||
|
||||
binEncoder :: PGScalarValue -> Q.PrepArg
|
||||
@@ -157,18 +177,20 @@ binEncoder colVal = case colVal of
|
||||
PGValJSON u -> Q.toPrepVal u
|
||||
PGValJSONB u -> Q.toPrepVal u
|
||||
PGValGeo o -> Q.toPrepVal $ TL.toStrict $ AE.encodeToLazyText o
|
||||
PGValRaster r -> Q.toPrepVal $ TC.toText $ getRasterWKB r
|
||||
PGValUnknown t -> (PTI.auto, Just (TE.encodeUtf8 t, PQ.Text))
|
||||
|
||||
txtEncoder :: PGScalarValue -> S.SQLExp
|
||||
txtEncoder colVal = case txtEncodedPGVal colVal of
|
||||
TENull -> S.SEUnsafe "NULL"
|
||||
TENull -> S.SENull
|
||||
TELit t -> S.SELit t
|
||||
|
||||
toPrepParam :: Int -> PGScalarType -> S.SQLExp
|
||||
toPrepParam i ty = withGeoVal ty $ S.SEPrep i
|
||||
toPrepParam i ty = withConstructorFn ty $ S.SEPrep i
|
||||
|
||||
toBinaryValue :: WithScalarType PGScalarValue -> Q.PrepArg
|
||||
toBinaryValue = binEncoder . pstValue
|
||||
|
||||
toTxtValue :: WithScalarType PGScalarValue -> S.SQLExp
|
||||
toTxtValue (WithScalarType ty val) = S.withTyAnn ty . withGeoVal ty $ txtEncoder val
|
||||
toTxtValue (WithScalarType ty val) =
|
||||
S.withTyAnn ty . withConstructorFn ty $ txtEncoder val
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
description: Fetch raster values which intersects the input geometry
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
dummy_rast:
|
||||
- rid: 1
|
||||
rast: 01000001009A9999999999E93F9A9999999999E9BF000000000000F0BF000000000000104000000000000000000000000000000000E610000005000500440000010101000101010101010101010101010101010001010100
|
||||
- rid: 2
|
||||
rast: 0100000100166C8E335B91F13FE2385B00285EF6BF360EE40064EBFFBF8D033900D9FA134000000000000000000000000000000000E610000005000500440000000101010001010101010101010101010101000101010000
|
||||
|
||||
query:
|
||||
variables:
|
||||
point:
|
||||
type: Point
|
||||
coordinates:
|
||||
- 1
|
||||
- 2
|
||||
crs:
|
||||
type: name
|
||||
properties:
|
||||
name: urn:ogc:def:crs:EPSG::4326
|
||||
|
||||
query: |
|
||||
query ($point: geometry!){
|
||||
dummy_rast(where: {rast: {_st_intersects_geom_nband: {geommin: $point}}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
description: Fetch raster values which intersects the input geometry
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
dummy_rast: []
|
||||
|
||||
query:
|
||||
variables:
|
||||
point:
|
||||
type: Point
|
||||
coordinates:
|
||||
- 4
|
||||
- 4
|
||||
crs:
|
||||
type: name
|
||||
properties:
|
||||
name: urn:ogc:def:crs:EPSG::4326
|
||||
|
||||
query: |
|
||||
query ($point: geometry!){
|
||||
dummy_rast(where: {rast: {_st_intersects_geom_nband: {geommin: $point}}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
description: Fetch raster values which intersects the input raster
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
dummy_rast:
|
||||
- rid: 1
|
||||
rast: 01000001009A9999999999E93F9A9999999999E9BF000000000000F0BF000000000000104000000000000000000000000000000000E610000005000500440000010101000101010101010101010101010101010001010100
|
||||
- rid: 2
|
||||
rast: 0100000100166C8E335B91F13FE2385B00285EF6BF360EE40064EBFFBF8D033900D9FA134000000000000000000000000000000000E610000005000500440000000101010001010101010101010101010101000101010000
|
||||
|
||||
query:
|
||||
variables:
|
||||
rast: '0100000100000000000000004000000000000000C00000000000000000000000000000084000000000000000000000000000000000E610000001000100440001'
|
||||
query: |
|
||||
query ($rast: raster){
|
||||
dummy_rast(where: {rast: {_st_intersects_rast: $rast}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
description: Fetch raster values which intersects the input raster
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
errors:
|
||||
- extensions:
|
||||
path: "$.variableValues.rast"
|
||||
code: parse-failed
|
||||
message: invalid hexadecimal representation of raster well known binary format
|
||||
|
||||
query:
|
||||
variables:
|
||||
rast: 'this is invalid raster value'
|
||||
query: |
|
||||
query ($rast: raster){
|
||||
dummy_rast(where: {rast: {_st_intersects_rast: $rast}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
description: Fetch raster values which intersects the input raster
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
dummy_rast: []
|
||||
|
||||
query:
|
||||
variables:
|
||||
rast: '0100000100000000000000004000000000000000C00000000000002240000000000000264000000000000000000000000000000000E610000001000100440001'
|
||||
query: |
|
||||
query ($rast: raster){
|
||||
dummy_rast(where: {rast: {_st_intersects_rast: $rast}}){
|
||||
rid
|
||||
rast
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#Create required extensions, tables and insert test data
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis_topology;
|
||||
|
||||
CREATE TABLE dummy_rast(
|
||||
rid serial primary key,
|
||||
rast raster
|
||||
);
|
||||
INSERT INTO dummy_rast (rast) values
|
||||
(ST_AsRaster(ST_Buffer(ST_GeomFromText('SRID=4326;POINT(1 2)'),2), 5, 5))
|
||||
, (ST_AsRaster(ST_Buffer(ST_GeomFromText('SRID=4326;LINESTRING(0 0, 0.5 1, 1 2, 1.5 3)'), 2), 5, 5))
|
||||
;
|
||||
- type: track_table
|
||||
args:
|
||||
name: dummy_rast
|
||||
schema: public
|
||||
@@ -0,0 +1,7 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
DROP TABLE dummy_rast;
|
||||
cascade: true
|
||||
@@ -353,6 +353,28 @@ class TestGraphQLQueryBoolExpPostGIS(DefaultTestSelectQueries):
|
||||
def dir(cls):
|
||||
return 'queries/graphql_query/boolexp/postgis'
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
class TestGraphQLQueryBoolExpRaster(DefaultTestSelectQueries):
|
||||
|
||||
def test_query_st_intersects_geom_nband(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/query_st_intersects_geom_nband.yaml', transport)
|
||||
|
||||
def test_query_st_intersects_geom_nband_no_rows(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/query_st_intersects_geom_nband_no_rows.yaml', transport)
|
||||
|
||||
def test_query_st_intersects_rast(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/query_st_intersects_rast.yaml', transport)
|
||||
|
||||
def test_query_st_intersects_rast_no_rows(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/query_st_intersects_rast_no_rows.yaml', transport)
|
||||
|
||||
def test_query_st_intersects_rast_fail(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/query_st_intersects_rast_fail.yaml', transport)
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return 'queries/graphql_query/boolexp/raster'
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
class TestGraphQLQueryOrderBy(DefaultTestSelectQueries):
|
||||
def test_articles_order_by_without_id(self, hge_ctx, transport):
|
||||
|
||||
Reference in New Issue
Block a user