diff --git a/CHANGELOG.md b/CHANGELOG.md index 1201922e1ab..b62e2882290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,51 @@ ## Next release (Add entries here in the order of: server, console, cli, docs, others) +### Support geometry and geography spatial data comparison operators in MS SQL Server + +Comparison operators on spatial data types, geometry and geography, are now supported in MS SQL Server. The following operators are supported: + +- STEquals +- STIntersects +- STTouches +- STOverlaps +- STCrosses +- STWithin +- STContains + +**Example query:** Select values equal to a given geography instance + +``` +query { + spatial_types_geog( + where: { + point: { _st_equals: "POINT(3 4)" } + } + ) { + point + } +} +``` + +**Example query:** Select values that spatially contain a given geometry instance + +``` +query { + spatial_types_geom( + where: { + compoundcurve: { _st_contains: "POINT(0.5 0)" } + } + ) { + compoundcurve + } +} +``` ## v2.0.0-alpha.6 - server: fix action output type schema generation (fix #6631) - server/mssql: `mssql_add_source` can now take connection strings from environment variables -- server: support `IN`, `NIN`, `LIKE` and `NLIKE` operators in MSSQL +- server: support `IN`, `NIN`, `LIKE` and `NLIKE` operators in MS SQL Server - server: remove the restriction of supporting only base type function arguments. The type of an argument with a table type is now `_scalar` to avoid conflicts with the object type ``. - server: fix inherited_roles issue when some of the underlying roles don't have permissions configured (fixes #6672) - server: fix action custom types failing to parse when mutually recursive diff --git a/server/src-lib/Hasura/Backends/MSSQL/DDL/BoolExp.hs b/server/src-lib/Hasura/Backends/MSSQL/DDL/BoolExp.hs index b80bce9d37b..0ff36f5a6d6 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/DDL/BoolExp.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/DDL/BoolExp.hs @@ -39,37 +39,53 @@ parseBoolExpOperations rhsParser _fields columnInfo value = parseOperation :: ColumnType 'MSSQL -> (Text, J.Value) -> m (OpExpG 'MSSQL v) parseOperation columnType (opStr, val) = withPathK opStr $ case opStr of - "_eq" -> parseEq - "$eq" -> parseEq + "_eq" -> parseEq + "$eq" -> parseEq - "_neq" -> parseNeq - "$neq" -> parseNeq + "_neq" -> parseNeq + "$neq" -> parseNeq - "$in" -> parseIn - "_in" -> parseIn + "$in" -> parseIn + "_in" -> parseIn - "$nin" -> parseNin - "_nin" -> parseNin + "$nin" -> parseNin + "_nin" -> parseNin - "_gt" -> parseGt - "$gt" -> parseGt + "_gt" -> parseGt + "$gt" -> parseGt - "_lt" -> parseLt - "$lt" -> parseLt + "_lt" -> parseLt + "$lt" -> parseLt - "_gte" -> parseGte - "$gte" -> parseGte + "_gte" -> parseGte + "$gte" -> parseGte - "_lte" -> parseLte - "$lte" -> parseLte + "_lte" -> parseLte + "$lte" -> parseLte - "$like" -> parseLike - "_like" -> parseLike + "$like" -> parseLike + "_like" -> parseLike - "$nlike" -> parseNlike - "_nlike" -> parseNlike + "$nlike" -> parseNlike + "_nlike" -> parseNlike - x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x + "_st_contains" -> parseGeometryOrGeographyOp ASTContains + "$st_contains" -> parseGeometryOrGeographyOp ASTContains + "_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_within" -> parseGeometryOrGeographyOp ASTWithin + "$st_within" -> parseGeometryOrGeographyOp ASTWithin + + "_st_crosses" -> parseGeometryOp ASTCrosses + "$st_crosses" -> parseGeometryOp ASTCrosses + "_st_touches" -> parseGeometryOp ASTTouches + "$st_touches" -> parseGeometryOp ASTTouches + + x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x where colTy = pgiType columnInfo @@ -88,6 +104,12 @@ parseBoolExpOperations rhsParser _fields columnInfo value = parseLike = guardType stringTypes >> ALIKE <$> parseOne parseNlike = guardType stringTypes >> ANLIKE <$> parseOne + parseGeometryOp f = + guardType [GeometryType] >> f <$> parseOneNoSess colTy val + parseGeometryOrGeographyOp f = + guardType geoTypes >> f <$> parseOneNoSess colTy val + parseOneNoSess ty = rhsParser (CollectableTypeScalar ty) + guardType validTys = unless (isScalarColumnWhere (`elem` validTys) colTy) $ throwError $ buildMsg colTy validTys diff --git a/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs b/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs index 467113bb320..819e4eeb363 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs @@ -818,6 +818,22 @@ fromOpExpG expression op = IR.ANIN val -> pure (OpExpression TSQL.NIN expression val) IR.ALIKE val -> pure (OpExpression TSQL.LIKE expression val) IR.ANLIKE val -> pure (OpExpression TSQL.NLIKE expression val) + IR.ASTContains val -> pure (TSQL.STOpExpression TSQL.STContains expression val) + IR.ASTCrosses val -> pure (TSQL.STOpExpression TSQL.STCrosses expression val) + IR.ASTEquals val -> pure (TSQL.STOpExpression TSQL.STEquals expression val) + IR.ASTIntersects val -> pure (TSQL.STOpExpression TSQL.STIntersects expression val) + IR.ASTOverlaps val -> pure (TSQL.STOpExpression TSQL.STOverlaps expression val) + IR.ASTTouches val -> pure (TSQL.STOpExpression TSQL.STTouches expression val) + IR.ASTWithin val -> pure (TSQL.STOpExpression TSQL.STWithin expression val) + -- https://docs.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server + IR.AAncestor _ -> refute (pure (UnsupportedOpExpG op)) + IR.AAncestorAny _ -> refute (pure (UnsupportedOpExpG op)) + IR.ADescendant _ -> refute (pure (UnsupportedOpExpG op)) + IR.ADescendantAny _ -> refute (pure (UnsupportedOpExpG op)) + IR.AMatches _ -> refute (pure (UnsupportedOpExpG op)) + IR.AMatchesAny _ -> refute (pure (UnsupportedOpExpG op)) + IR.AMatchesFulltext _ -> refute (pure (UnsupportedOpExpG op)) + -- No equivalent operators in SQL Server for the following -- https://docs.microsoft.com/en-us/sql/t-sql/functions/json-functions-transact-sql IR.AContains _val -> refute (pure (UnsupportedOpExpG op)) IR.AContainedIn _val -> refute (pure (UnsupportedOpExpG op)) @@ -831,35 +847,19 @@ fromOpExpG expression op = IR.AIREGEX _val -> refute (pure (UnsupportedOpExpG op)) IR.ANREGEX _val -> refute (pure (UnsupportedOpExpG op)) IR.ANIREGEX _val -> refute (pure (UnsupportedOpExpG op)) - -- https://docs.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server - IR.AAncestor _ -> refute (pure (UnsupportedOpExpG op)) - IR.AAncestorAny _ -> refute (pure (UnsupportedOpExpG op)) - IR.ADescendant _ -> refute (pure (UnsupportedOpExpG op)) - IR.ADescendantAny _ -> refute (pure (UnsupportedOpExpG op)) - IR.AMatches _ -> refute (pure (UnsupportedOpExpG op)) - IR.AMatchesAny _ -> refute (pure (UnsupportedOpExpG op)) - IR.AMatchesFulltext _ -> refute (pure (UnsupportedOpExpG op)) - -- As of March 2021, only geometry/geography casts are supported - IR.ACast _casts -> refute (pure (UnsupportedOpExpG op)) -- mkCastsExp casts -- https://docs.microsoft.com/en-us/sql/relational-databases/spatial/spatial-data-sql-server - IR.ASTContains _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Contains" val - IR.ASTCrosses _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Crosses" val - IR.ASTEquals _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Equals" val - IR.ASTIntersects _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Intersects" val - IR.ASTOverlaps _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Overlaps" val - IR.ASTTouches _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Touches" val - IR.ASTWithin _val -> refute (pure (UnsupportedOpExpG op)) -- mkGeomOpBe "ST_Within" val - IR.ASTDWithinGeom {} -> refute (pure (UnsupportedOpExpG op)) -- applySQLFn "ST_DWithin" [lhs, val, r] - IR.ASTDWithinGeog {} -> refute (pure (UnsupportedOpExpG op)) -- applySQLFn "ST_DWithin" [lhs, val, r, sph] - IR.ASTIntersectsRast _val -> refute (pure (UnsupportedOpExpG op)) -- applySTIntersects [lhs, val] - IR.ASTIntersectsNbandGeom {} -> refute (pure (UnsupportedOpExpG op)) -- applySTIntersects [lhs, nband, geommin] - IR.ASTIntersectsGeomNband {} -> refute (pure (UnsupportedOpExpG op)) -- applySTIntersects [lhs, geommin, withSQLNull mNband] - IR.CEQ _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SEQ lhs $ mkQCol rhsCol - IR.CNE _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SNE lhs $ mkQCol rhsCol - IR.CGT _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SGT lhs $ mkQCol rhsCol - IR.CLT _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SLT lhs $ mkQCol rhsCol - IR.CGTE _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SGTE lhs $ mkQCol rhsCol - IR.CLTE _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SLTE lhs $ mkQCol rhsCol + IR.ACast _casts -> refute (pure (UnsupportedOpExpG op)) + IR.ASTDWithinGeom {} -> refute (pure (UnsupportedOpExpG op)) + IR.ASTDWithinGeog {} -> refute (pure (UnsupportedOpExpG op)) + IR.ASTIntersectsRast _val -> refute (pure (UnsupportedOpExpG op)) + IR.ASTIntersectsNbandGeom {} -> refute (pure (UnsupportedOpExpG op)) + IR.ASTIntersectsGeomNband {} -> refute (pure (UnsupportedOpExpG op)) + IR.CEQ _rhsCol -> refute (pure (UnsupportedOpExpG op)) + IR.CNE _rhsCol -> refute (pure (UnsupportedOpExpG op)) + IR.CGT _rhsCol -> refute (pure (UnsupportedOpExpG op)) + IR.CLT _rhsCol -> refute (pure (UnsupportedOpExpG op)) + IR.CGTE _rhsCol -> refute (pure (UnsupportedOpExpG op)) + IR.CLTE _rhsCol -> refute (pure (UnsupportedOpExpG op)) nullableBoolEquality :: Expression -> Expression -> Expression nullableBoolEquality x y = diff --git a/server/src-lib/Hasura/Backends/MSSQL/Instances/Schema.hs b/server/src-lib/Hasura/Backends/MSSQL/Instances/Schema.hs index 7328337f4c8..672488c9c04 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/Instances/Schema.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/Instances/Schema.hs @@ -323,6 +323,35 @@ msComparisonExps = P.memoize 'comparisonExps \columnType -> do (Just "does the column NOT match the given pattern") (ANLIKE . mkParameter <$> typedParser) ] + + -- Ops for Geometry/Geography types + , guard (isScalarColumnWhere (`elem` MSSQL.geoTypes) columnType) *> + [ P.fieldOptional $$(G.litName "_st_contains") + (Just "does the column contain the given value") + (ASTContains . mkParameter <$> typedParser) + , P.fieldOptional $$(G.litName "_st_equals") + (Just "is the column equal to given value (directionality is ignored)") + (ASTEquals . mkParameter <$> typedParser) + , P.fieldOptional $$(G.litName "_st_intersects") + (Just "does the column spatially intersect the given value") + (ASTIntersects . mkParameter <$> typedParser) + , P.fieldOptional $$(G.litName "_st_overlaps") + (Just "does the column 'spatially overlap' (intersect but not completely contain) the given value") + (ASTOverlaps . mkParameter <$> typedParser) + , P.fieldOptional $$(G.litName "_st_within") + (Just "is the column contained in the given value") + (ASTWithin . mkParameter <$> typedParser) + ] + + -- Ops for Geometry types + , guard (isScalarColumnWhere (MSSQL.GeometryType ==) columnType) *> + [ P.fieldOptional $$(G.litName "_st_crosses") + (Just "does the column cross the given geometry value") + (ASTCrosses . mkParameter <$> typedParser) + , P.fieldOptional $$(G.litName "_st_touches") + (Just "does the column have at least one point in common with the given geometry value") + (ASTTouches . mkParameter <$> typedParser) + ] ] where mkListLiteral :: [ColumnValue 'MSSQL] -> UnpreparedValue 'MSSQL diff --git a/server/src-lib/Hasura/Backends/MSSQL/ToQuery.hs b/server/src-lib/Hasura/Backends/MSSQL/ToQuery.hs index a716d367d88..9af23296f73 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/ToQuery.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/ToQuery.hs @@ -102,6 +102,10 @@ fromExpression = fromExpression x <+> ") " <+> fromOp op <+> " (" <+> fromExpression y <+> ")" ListExpression xs -> SepByPrinter ", " $ fromExpression <$> xs + STOpExpression op e str -> + "(" <+> fromExpression e <+> ")." <+> + fromString (show op) <+> + "(" <+> fromExpression str <+> ") = 1" fromOp :: Op -> Printer fromOp = diff --git a/server/src-lib/Hasura/Backends/MSSQL/Types/Instances.hs b/server/src-lib/Hasura/Backends/MSSQL/Types/Instances.hs index 991423c155d..37d67ca51f2 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/Types/Instances.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/Types/Instances.hs @@ -69,6 +69,7 @@ $(fmap concat $ for [ ''Where , ''FieldName , ''JsonPath , ''Op + , ''SpatialOp , ''Projection , ''From , ''OpenJson diff --git a/server/src-lib/Hasura/Backends/MSSQL/Types/Internal.hs b/server/src-lib/Hasura/Backends/MSSQL/Types/Internal.hs index 79642ea00d8..aab2dedc804 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/Types/Internal.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/Types/Internal.hs @@ -165,6 +165,7 @@ data Expression -- string. | OpExpression Op Expression Expression | ListExpression [Expression] + | STOpExpression SpatialOp Expression Expression data JsonPath = RootPath @@ -231,6 +232,16 @@ data Op | NLIKE | NIN +-- | Supported operations for spatial data types +data SpatialOp + = STEquals + | STContains + | STCrosses + | STIntersects + | STOverlaps + | STWithin + | STTouches + -- | Column name of some database table -- this differs to FieldName -- that is used for referring to things within a query. newtype ColumnName = ColumnName { columnNameText :: Text } @@ -376,3 +387,6 @@ stringTypes = , WvarcharType , WtextType ] + +geoTypes :: [ScalarType] +geoTypes = [GeometryType, GeographyType] diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_mssql.yaml new file mode 100644 index 00000000000..057c361d149 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_mssql.yaml @@ -0,0 +1,100 @@ +type: bulk +args: + - type: mssql_run_sql + args: + source: mssql + sql: | + CREATE TABLE spatial_types_geom ( + point geometry, + linestring geometry, + circularstring geometry, + compoundcurve geometry, + polygon geometry, + curvepolygon geometry, + multipoint geometry, + multilinestring geometry, + multipolygon geometry, + geometrycollection geometry + ); + + INSERT INTO + spatial_types_geom( + point, + linestring, + circularstring, + compoundcurve, + polygon, + curvepolygon, + multipoint, + multilinestring, + multipolygon, + geometrycollection + ) + VALUES + ( + geometry :: Parse('POINT(3 4 7 2.5)').MakeValid(), + geometry :: Parse('LINESTRING(1 1,2 3,4 8, -6 3)').MakeValid(), + geometry :: Parse('CIRCULARSTRING(1 1, 2 0, 2 0, 2 0, 1 1)').MakeValid(), + geometry :: Parse( + 'COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0))' + ).MakeValid(), + geometry :: Parse('POLYGON((1 1, 1 1, 1 1, 1 1))').MakeValid(), + geometry :: Parse( + 'CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094)))' + ).MakeValid(), + geometry :: Parse('MULTIPOINT((2 3), (7 8 9.5))').MakeValid(), + geometry :: Parse( + 'MULTILINESTRING((1 1, 3 3, 5 5),(3 3, 5 5, 7 7))' + ).MakeValid(), + geometry :: Parse( + 'MULTIPOLYGON(((-120.533 46.566, -118.283 46.1, -122.3 47.45, -120.533 46.566)),((2 2, 2 -2, -2 -2, -2 2, 2 2)),((1 1, 3 1, 3 3, 1 3, 1 1)))' + ).MakeValid(), + geometry :: Parse( + 'GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5),POLYGON((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1)))' + ).MakeValid() + ); + + CREATE TABLE spatial_types_geog ( + point geography, + linestring geography, + circularstring geography, + compoundcurve geography, + polygon geography, + curvepolygon geography, + multipoint geography, + multilinestring geography, + multipolygon geography + ); + + INSERT INTO + spatial_types_geog( + point, + linestring, + circularstring, + compoundcurve, + polygon, + curvepolygon, + multipoint, + multilinestring, + multipolygon + ) + VALUES + ( + geography :: Parse('POINT(3 4 7 2.5)').MakeValid(), + geography :: Parse('LINESTRING(1 1,2 3,4 8, -6 3)').MakeValid(), + geography :: Parse('CIRCULARSTRING(1 1, 2 0, 2 0, 2 0, 1 1)').MakeValid(), + geography :: Parse( + 'COMPOUNDCURVE(CIRCULARSTRING(1 0, 0 1, -1 0), (-1 0, 2 0))' + ).MakeValid(), + geography :: Parse('POLYGON((1 1, 1 1, 1 1, 1 1))').MakeValid(), + geography :: Parse( + 'CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094)))' + ).MakeValid(), + geography :: Parse('MULTIPOINT((2 3), (7 8 9.5))').MakeValid(), + geography :: Parse( + 'MULTILINESTRING((1 1, 3 3, 5 5),(3 3, 5 5, 7 7))' + ).MakeValid(), + geography :: Parse( + 'MULTIPOLYGON(((-120.533 46.566, -118.283 46.1, -122.3 47.45, -120.533 46.566)),((2 2, 2 -2, -2 -2, -2 2, 2 2)),((1 1, 3 1, 3 3, 1 3, 1 1)))' + ).MakeValid() + ) diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/schema_teardown_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_teardown_mssql.yaml new file mode 100644 index 00000000000..766aa726199 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_teardown_mssql.yaml @@ -0,0 +1,13 @@ +type: bulk +args: + - type: mssql_run_sql + args: + source: mssql + sql: | + drop table spatial_types_geom + + - type: mssql_run_sql + args: + source: mssql + sql: | + drop table spatial_types_geog diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_geom_where_st_crosses_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_geom_where_st_crosses_mssql.yaml new file mode 100644 index 00000000000..fc2b9ccc4f9 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_geom_where_st_crosses_mssql.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geom table +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geom: + - linestring: LINESTRING (1 1, 2 3, 4 8, -6 3) +query: + query: | + query { + spatial_types_geom( + where: { + linestring: { + _st_crosses: "LINESTRING(1.5 1, 0 2.5)" + } + } + ) { + linestring + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_mssql.yaml new file mode 100644 index 00000000000..e4b464a0343 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_mssql.yaml @@ -0,0 +1,93 @@ +description: GraphQL query to test different data types of SQL Server +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geom: + - point: POINT (3 4) + linestring: LINESTRING (1 1, 2 3, 4 8, -6 3) + circularstring: LINESTRING (1.0000000000000036 1, 2 3.5527136788005009E-15) + compoundcurve: COMPOUNDCURVE (CIRCULARSTRING (1 0, 0 1, -1 0), (-1 0, 2 0)) + polygon: POINT (1 1) + curvepolygon: + CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, + -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 + 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING + (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094))) + multipoint: MULTIPOINT ((2 3), (7 8)) + multilinestring: + LINESTRING (7.0000000000000071 7.0000000000000071, 4.9999999999999929 + 4.9999999999999929, 3.0000000000000213 3.0000000000000213, 1.0000000000000284 + 1.0000000000000284) + multipolygon: + MULTIPOLYGON (((-118.28299999999973 46.099999999999994, -122.29999999999953 + 47.450000000000074, -120.53299999999965 46.566000000000059, -118.28299999999973 + 46.099999999999994)), ((2.0000000000000213 1.0000000000001386, 3.0000000000000284 + 1.0000000000001386, 3.0000000000000284 3.0000000000002416, 1.0000000000000142 + 3.0000000000002416, 1.0000000000000142 2.000000000000103, 2.0000000000000213 + 2.000000000000103, 2.0000000000000213 1.0000000000001386)), ((-2.0000000000000142 + -1.9999999999997549, 2.0000000000000213 -1.9999999999997549, 2.0000000000000213 + 1.0000000000001386, 1.0000000000000142 1.0000000000001386, 1.0000000000000142 + 2.000000000000103, -2.0000000000000142 2.000000000000103, -2.0000000000000142 + -1.9999999999997549))) + geometrycollection: + GEOMETRYCOLLECTION (LINESTRING (1 1, 3 5), POLYGON ((-1 + -1, -1 -5, -5 -5, -5 -1, -1 -1))) + spatial_types_geog: + - point: POINT (3 4) + linestring: LINESTRING (1 1, 2 3, 4 8, -6 3) + circularstring: + LINESTRING (0.99999999999996858 1.0000000000001434, 2.0000000000000311 + -1.4370884402691508E-13) + compoundcurve: COMPOUNDCURVE (CIRCULARSTRING (1 0, 0 1, -1 0), (-1 0, 2 0)) + polygon: POINT (1 0.99999999999999978) + curvepolygon: + CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, + -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 + 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING + (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094))) + multipoint: MULTIPOINT ((2 3), (7 8)) + multilinestring: + LINESTRING (6.9999999999998526 6.99999999999996, 5.0000000000001332 + 5.0000000000000506, 2.9999999999999662 2.9999999999999454, 1.0000000000000222 + 0.99999999999995925) + multipolygon: + MULTIPOLYGON (((1.0000000000001976 2.9999999999999387, 1.0000000000000258 + 2.0009135502171214, 2.0000000000001763 1.9999999999999043, 2.00000000000012 + 1.00015229710434, 3.0000000000000724 1.0000000000000069, 2.9999999999998894 + 2.999999999999873, 1.0000000000001976 2.9999999999999387)), ((2.00000000000012 + 1.00015229710434, 0.99999999999985156 1.0000000000002536, 1.0000000000000258 + 2.0009135502171214, -1.9999999999998943 2.000000000000087, -2.0000000000000604 + -1.9999999999998446, 1.9999999999999543 -2.0000000000001057, 2.00000000000012 + 1.00015229710434)), ((-118.2829999999997 46.099999999999874, -122.29999999999957 + 47.449999999999896, -120.53299999999994 46.566000000000038, -118.2829999999997 + 46.099999999999874))) + +query: + query: | + query { + spatial_types_geom { + point + linestring + circularstring + compoundcurve + polygon + curvepolygon + multipoint + multilinestring + multipolygon + geometrycollection + } + + spatial_types_geog { + point + linestring + circularstring + compoundcurve + polygon + curvepolygon + multipoint + multilinestring + multipolygon + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_mssql.yaml new file mode 100644 index 00000000000..e9824fd8bdc --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_mssql.yaml @@ -0,0 +1,32 @@ +description: Query data from spatial_types_geo* tables +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geog: + - multipoint: MULTIPOINT ((2 3), (7 8)) + spatial_types_geom: + - compoundcurve: COMPOUNDCURVE (CIRCULARSTRING (1 0, 0 1, -1 0), (-1 0, 2 0)) +query: + query: | + query { + spatial_types_geog( + where: { + multipoint: { + _st_contains: "POINT(2 3)" + } + } + ) { + multipoint + } + + spatial_types_geom( + where: { + compoundcurve: { + _st_contains: "POINT(0.5 0)" + } + } + ) { + compoundcurve + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_mssql.yaml new file mode 100644 index 00000000000..bc08c346da9 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_mssql.yaml @@ -0,0 +1,25 @@ +description: Query data from spatial_types_geo* tables +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geog: + - point: POINT (3 4) + spatial_types_geom: + - geometrycollection: GEOMETRYCOLLECTION (LINESTRING (1 1, 3 5), POLYGON ((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1))) +query: + query: | + query { + spatial_types_geog(where: { point: { _st_equals: "POINT(3 4)" } }) { + point + } + spatial_types_geom( + where: { + geometrycollection: { + _st_equals: "GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5),POLYGON((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1)))" + } + } + ) { + geometrycollection + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_mssql.yaml new file mode 100644 index 00000000000..68de1aa1ef8 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_mssql.yaml @@ -0,0 +1,25 @@ +description: Query data from spatial_types_geo* tables +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geog: + - polygon: POINT (1 0.99999999999999978) + spatial_types_geom: + - linestring: LINESTRING (1 1, 2 3, 4 8, -6 3) +query: + query: | + query { + spatial_types_geog(where: { polygon: { _st_intersects: "LINESTRING(1 1, 0 1)" } }) { + polygon + } + spatial_types_geom( + where: { + linestring: { + _st_intersects: "POINT(1 1)" + } + } + ) { + linestring + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_overlaps_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_overlaps_mssql.yaml new file mode 100644 index 00000000000..b9d93cd8b14 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_overlaps_mssql.yaml @@ -0,0 +1,46 @@ +description: Query data from spatial_types_geo* tables +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geom: + - multipolygon: + MULTIPOLYGON (((-118.28299999999973 46.099999999999994, -122.29999999999953 + 47.450000000000074, -120.53299999999965 46.566000000000059, -118.28299999999973 + 46.099999999999994)), ((2.0000000000000213 1.0000000000001386, 3.0000000000000284 + 1.0000000000001386, 3.0000000000000284 3.0000000000002416, 1.0000000000000142 + 3.0000000000002416, 1.0000000000000142 2.000000000000103, 2.0000000000000213 + 2.000000000000103, 2.0000000000000213 1.0000000000001386)), ((-2.0000000000000142 + -1.9999999999997549, 2.0000000000000213 -1.9999999999997549, 2.0000000000000213 + 1.0000000000001386, 1.0000000000000142 1.0000000000001386, 1.0000000000000142 + 2.000000000000103, -2.0000000000000142 2.000000000000103, -2.0000000000000142 + -1.9999999999997549))) + spatial_types_geog: + - curvepolygon: + CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, + -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 + 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING + (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094))) +query: + query: | + query { + spatial_types_geom( + where: { + multipolygon: { + _st_overlaps: "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 46.183634), (-119.119263 46.183634, -119.273071 47.107523), CIRCULARSTRING (-119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094)))" + } + } + ) { + multipolygon + } + + spatial_types_geog( + where: { + curvepolygon: { + _st_overlaps: "POLYGON((-120.533 46.566, -118.283 46.1, -122.3 47.45, -120.533 46.566))" + } + } + ) { + curvepolygon + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_mssql.yaml new file mode 100644 index 00000000000..698c47bb669 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_mssql.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geom table +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geom: + - point: POINT (3 4) +query: + query: | + query { + spatial_types_geom( + where: { + point: { + _st_touches: "POLYGON ((3 4, 2 5, 5 5, 5 2, 3 4))" + } + } + ) { + point + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_mssql.yaml new file mode 100644 index 00000000000..1ce476f0ff6 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_mssql.yaml @@ -0,0 +1,32 @@ +description: Query data from spatial_types_geo* tables +url: /v1/graphql +status: 200 +response: + data: + spatial_types_geom: + - point: POINT (3 4) + spatial_types_geog: + - multipoint: MULTIPOINT ((2 3), (7 8)) +query: + query: | + query { + spatial_types_geom( + where: { + point: { + _st_within: "POLYGON ((2 2, 2 5, 5 5, 5 2, 2 2))" + } + } + ) { + point + } + + spatial_types_geog( + where: { + multipoint: { + _st_within: "POLYGON ((0 0, 10 10, 10 10, 10 0, 0 0))" + } + } + ) { + multipoint + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/setup.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/setup.yaml new file mode 100644 index 00000000000..3ef68822606 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/setup.yaml @@ -0,0 +1,3 @@ +# TODO: https://github.com/hasura/graphql-engine-mono/issues/941 +type: bulk +args: [] diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/setup_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/setup_mssql.yaml new file mode 100644 index 00000000000..d958124cfbb --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/setup_mssql.yaml @@ -0,0 +1,13 @@ +type: bulk +args: + - type: mssql_track_table + args: + source: mssql + table: + name: spatial_types_geom + + - type: mssql_track_table + args: + source: mssql + table: + name: spatial_types_geog diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/teardown.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown.yaml new file mode 100644 index 00000000000..3ef68822606 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown.yaml @@ -0,0 +1,3 @@ +# TODO: https://github.com/hasura/graphql-engine-mono/issues/941 +type: bulk +args: [] diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_mssql.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_mssql.yaml new file mode 100644 index 00000000000..54fdb9f6898 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_mssql.yaml @@ -0,0 +1,2 @@ +type: bulk +args: [] diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 8547734f777..d9ecfe8a4e2 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -954,3 +954,36 @@ class TestGraphQLQueryBoolExpLtree: @classmethod def dir(cls): return 'queries/graphql_query/boolexp/ltree' + +@pytest.mark.parametrize("transport", ['http', 'websocket']) +@pytest.mark.parametrize("backend", ['mssql']) +@usefixtures('per_class_tests_db_state', 'per_backend_tests') +class TestGraphQLQueryBoolExpSpatialMSSQL: + @pytest.mark.skip_server_upgrade_test + def test_select_spatial_mssql_types(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_equals(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_equals_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_contains(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_contains_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_crosses(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_geom_where_st_crosses_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_intersects(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_intersects_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_overlaps(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_overlaps_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_within(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_within_mssql.yaml', transport) + + def test_select_spatial_mssql_types_where_st_touches(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_touches_mssql.yaml', transport) + + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/spatial'