support intersect filters on raster columns (close #2613) (#2704)

* 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:
Rakesh Emmadi
2019-08-29 18:37:05 +05:30
committed by Shahidh K Muhammed
parent 7f818b8b19
commit f7c99689da
24 changed files with 533 additions and 23 deletions

View File

@@ -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

View File

@@ -1508,3 +1508,157 @@ Columns of type ``geography`` are more accurate, but they dont 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"
}
}
}
}

View File

@@ -94,6 +94,7 @@ library
-- String related
, case-insensitive
, string-conversions
, text-conversions
-- Http client
, wreq

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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)

View File

@@ -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) =

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -0,0 +1,7 @@
type: bulk
args:
- type: run_sql
args:
sql: |
DROP TABLE dummy_rast;
cascade: true

View File

@@ -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):