From 47b9321ba45ab673cee0d27bb08e01e97902d250 Mon Sep 17 00:00:00 2001 From: Kirill Zaborsky Date: Tue, 18 Jan 2022 16:33:27 +0300 Subject: [PATCH] BigQuery operators: like/nlike, geography PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2924 GitOrigin-RevId: 3748411cb4419a21a58283836adb5cb39d351d19 --- CHANGELOG.md | 1 + .../Hasura/Backends/BigQuery/FromIr.hs | 17 ++- .../Backends/BigQuery/Instances/Schema.hs | 109 +++++++++++++++--- .../Backends/BigQuery/Instances/Types.hs | 2 +- .../Hasura/Backends/BigQuery/ToQuery.hs | 6 +- .../src-lib/Hasura/Backends/BigQuery/Types.hs | 32 ++++- .../boolexp/search/schema_setup_bigquery.yaml | 18 +++ .../select_city_where_like_bigquery.yaml | 21 ++++ .../select_city_where_nlike_bigquery.yaml | 21 ++++ .../boolexp/search/setup_bigquery.yaml | 10 ++ .../boolexp/search/teardown_bigquery.yaml | 10 ++ .../spatial/schema_setup_bigquery.yaml | 45 ++++++++ .../select_query_spatial_types_bigquery.yaml | 30 +++++ ...tial_types_where_st_contains_bigquery.yaml | 21 ++++ ...tial_types_where_st_d_within_bigquery.yaml | 20 ++++ ...atial_types_where_st_dwithin_bigquery.yaml | 20 ++++ ...patial_types_where_st_equals_bigquery.yaml | 24 ++++ ...al_types_where_st_intersects_bigquery.yaml | 14 +++ ...atial_types_where_st_touches_bigquery.yaml | 20 ++++ ...patial_types_where_st_within_bigquery.yaml | 20 ++++ .../boolexp/spatial/setup_bigquery.yaml | 9 ++ .../boolexp/spatial/teardown_bigquery.yaml | 10 ++ server/tests-py/test_graphql_queries.py | 43 +++++++ 23 files changed, 504 insertions(+), 19 deletions(-) create mode 100644 server/tests-py/queries/graphql_query/boolexp/search/schema_setup_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/search/select_city_where_like_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/search/select_city_where_nlike_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/search/setup_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/search/teardown_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_d_within_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_dwithin_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/setup_bigquery.yaml create mode 100644 server/tests-py/queries/graphql_query/boolexp/spatial/teardown_bigquery.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index c365f6ab845..3740f3823fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ - cli: fix cli-console failing to add migrations if there are tabs in SQL body (#7362) - cli: sign windows binary of Hasura CLI (#7147) - cli: core CLI features are not blocked in environments without internet (#7695) +- server: add `_like`/`_nlike` and spatial operators for BigQuery ## v2.1.0-beta.2 diff --git a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs index d8a0a0e0aca..7c8e80b68c2 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs @@ -1544,8 +1544,9 @@ fromOpExpG expression op = Ir.AGTE val -> pure (OpExpression MoreOrEqualOp expression val) Ir.ALTE val -> pure (OpExpression LessOrEqualOp expression val) Ir.ACast _casts -> refute (pure (UnsupportedOpExpG op)) -- mkCastsExp casts - Ir.ALIKE _val -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SLIKE lhs val - Ir.ANLIKE _val -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SNLIKE lhs val + Ir.ALIKE val -> pure (OpExpression LikeOp expression val) + Ir.ANLIKE val -> pure (OpExpression NotLikeOp expression val) + Ir.ABackendSpecific op' -> pure (fromBackendSpecificOpExpG expression op') 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 @@ -1554,6 +1555,18 @@ fromOpExpG expression op = Ir.CLTE _rhsCol -> refute (pure (UnsupportedOpExpG op)) -- S.BECompare S.SLTE lhs $ mkQCol rhsCol -- These are new as of 2021-02-18 to this API. Not sure what to do with them at present, marking as unsupported. +fromBackendSpecificOpExpG :: Expression -> BigQuery.BooleanOperators Expression -> Expression +fromBackendSpecificOpExpG expression op = + let func name val = FunctionExpression name [expression, val] + in case op of + BigQuery.ASTContains v -> func "ST_CONTAINS" v + BigQuery.ASTEquals v -> func "ST_EQUALS" v + BigQuery.ASTTouches v -> func "ST_TOUCHES" v + BigQuery.ASTWithin v -> func "ST_WITHIN" v + BigQuery.ASTIntersects v -> func "ST_INTERSECTS" v + BigQuery.ASTDWithin (Ir.DWithinGeogOp r v sph) -> + FunctionExpression "ST_DWITHIN" [expression, v, r, sph] + nullableBoolEquality :: Expression -> Expression -> Expression nullableBoolEquality x y = OrExpression diff --git a/server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs b/server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs index d80ea0c2610..fcf7d78aca2 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs @@ -157,7 +157,7 @@ bqColumnParser columnType (G.Nullability isNullable) = ColumnScalar scalarType -> case scalarType of -- bytestrings -- we only accept string literals - BigQuery.BytesScalarType -> pure $ possiblyNullable scalarType $ BigQuery.StringValue <$> P.string + BigQuery.BytesScalarType -> pure $ possiblyNullable scalarType $ BigQuery.StringValue <$> stringBased $$(G.litName "Bytes") -- text BigQuery.StringScalarType -> pure $ possiblyNullable scalarType $ BigQuery.StringValue <$> P.string -- floating point values @@ -171,12 +171,13 @@ bqColumnParser columnType (G.Nullability isNullable) = BigQuery.BigDecimalScalarType -> pure $ possiblyNullable scalarType $ BigQuery.BigDecimalValue . BigQuery.doubleToBigDecimal <$> P.float -- boolean type BigQuery.BoolScalarType -> pure $ possiblyNullable scalarType $ BigQuery.BoolValue <$> P.boolean - BigQuery.DateScalarType -> pure $ possiblyNullable scalarType $ BigQuery.DateValue . BigQuery.Date <$> P.string - BigQuery.TimeScalarType -> pure $ possiblyNullable scalarType $ BigQuery.TimeValue . BigQuery.Time <$> P.string - BigQuery.DatetimeScalarType -> pure $ possiblyNullable scalarType $ BigQuery.DatetimeValue . BigQuery.Datetime <$> P.string - BigQuery.GeographyScalarType -> pure $ possiblyNullable scalarType $ BigQuery.GeographyValue . BigQuery.Geography <$> P.string + BigQuery.DateScalarType -> pure $ possiblyNullable scalarType $ BigQuery.DateValue . BigQuery.Date <$> stringBased $$(G.litName "Date") + BigQuery.TimeScalarType -> pure $ possiblyNullable scalarType $ BigQuery.TimeValue . BigQuery.Time <$> stringBased $$(G.litName "Time") + BigQuery.DatetimeScalarType -> pure $ possiblyNullable scalarType $ BigQuery.DatetimeValue . BigQuery.Datetime <$> stringBased $$(G.litName "Datetime") + BigQuery.GeographyScalarType -> + pure $ possiblyNullable scalarType $ BigQuery.GeographyValue . BigQuery.Geography <$> throughJSON $$(G.litName "Geography") BigQuery.TimestampScalarType -> do - let schemaType = P.TNamed P.Nullable $ P.Definition stringScalar Nothing P.TIScalar + let schemaType = P.TNamed P.Nullable $ P.Definition $$(G.litName "Timestamp") Nothing P.TIScalar pure $ possiblyNullable scalarType $ Parser @@ -212,6 +213,9 @@ bqColumnParser columnType (G.Nullability isNullable) = valueToJSON (P.toGraphQLType schemaType) >=> either (parseErrorWith ParseFailed . qeError) pure . runAesonParser J.parseJSON } + stringBased :: MonadParse m => G.Name -> Parser 'Both m Text + stringBased scalarName = + P.string {pType = P.TNamed P.NonNullable $ P.Definition scalarName Nothing P.TIScalar} bqJsonPathArg :: MonadParse n => @@ -255,6 +259,7 @@ bqComparisonExps :: m (Parser 'Input n [ComparisonExp 'BigQuery]) bqComparisonExps = P.memoize 'comparisonExps $ \columnType -> do collapseIfNull <- asks $ qcDangerousBooleanCollapse . getter + dWithinGeogOpParser <- geographyWithinDistanceInput -- see Note [Columns in comparison expression are never nullable] typedParser <- columnParser columnType (G.Nullability False) nullableTextParser <- columnParser (ColumnScalar @'BigQuery BigQuery.StringScalarType) (G.Nullability True) @@ -275,15 +280,93 @@ bqComparisonExps = P.memoize 'comparisonExps $ \columnType -> do fmap catMaybes $ sequenceA $ concat - [ equalityOperators - collapseIfNull - (mkParameter <$> typedParser) - (mkListLiteral <$> columnListParser), - comparisonOperators - collapseIfNull - (mkParameter <$> typedParser) + [ -- from https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types: + -- GEOGRAPHY comparisons are not supported. To compare GEOGRAPHY values, use ST_Equals. + guard (isScalarColumnWhere (/= BigQuery.GeographyScalarType) columnType) + *> equalityOperators + collapseIfNull + (mkParameter <$> typedParser) + (mkListLiteral <$> columnListParser), + guard (isScalarColumnWhere (/= BigQuery.GeographyScalarType) columnType) + *> comparisonOperators + collapseIfNull + (mkParameter <$> typedParser), + -- Ops for String type + guard (isScalarColumnWhere (== BigQuery.StringScalarType) columnType) + *> [ mkBoolOperator + collapseIfNull + $$(G.litName "_like") + (Just "does the column match the given pattern") + (ALIKE . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_nlike") + (Just "does the column NOT match the given pattern") + (ANLIKE . mkParameter <$> typedParser) + ], + -- Ops for Bytes type + guard (isScalarColumnWhere (== BigQuery.BytesScalarType) columnType) + *> [ mkBoolOperator + collapseIfNull + $$(G.litName "_like") + (Just "does the column match the given pattern") + (ALIKE . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_nlike") + (Just "does the column NOT match the given pattern") + (ANLIKE . mkParameter <$> typedParser) + ], + -- Ops for Geography type + guard (isScalarColumnWhere (== BigQuery.GeographyScalarType) columnType) + *> [ mkBoolOperator + collapseIfNull + $$(G.litName "_st_contains") + (Just "does the column contain the given geography value") + (ABackendSpecific . BigQuery.ASTContains . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_st_equals") + (Just "is the column equal to given geography value (directionality is ignored)") + (ABackendSpecific . BigQuery.ASTEquals . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_st_touches") + (Just "does the column have at least one point in common with the given geography value") + (ABackendSpecific . BigQuery.ASTTouches . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_st_within") + (Just "is the column contained in the given geography value") + (ABackendSpecific . BigQuery.ASTWithin . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_st_intersects") + (Just "does the column spatially intersect the given geography value") + (ABackendSpecific . BigQuery.ASTIntersects . mkParameter <$> typedParser), + mkBoolOperator + collapseIfNull + $$(G.litName "_st_d_within") + (Just "is the column within a given distance from the given geometry value") + (ABackendSpecific . BigQuery.ASTDWithin <$> dWithinGeogOpParser) + ] ] +geographyWithinDistanceInput :: + forall m n r. + (MonadSchema n m, MonadError QErr m, MonadReader r m, Has MkTypename r) => + m (Parser 'Input n (DWithinGeogOp (UnpreparedValue 'BigQuery))) +geographyWithinDistanceInput = do + geographyParser <- columnParser (ColumnScalar BigQuery.GeographyScalarType) (G.Nullability False) + -- practically BigQuery (as of 2021-11-19) doesn't support TRUE as use_spheroid parameter for ST_DWITHIN + booleanParser <- columnParser (ColumnScalar BigQuery.BoolScalarType) (G.Nullability True) + floatParser <- columnParser (ColumnScalar BigQuery.FloatScalarType) (G.Nullability False) + pure $ + P.object $$(G.litName "st_dwithin_input") Nothing $ + DWithinGeogOp <$> (mkParameter <$> P.field $$(G.litName "distance") Nothing floatParser) + <*> (mkParameter <$> P.field $$(G.litName "from") Nothing geographyParser) + <*> (mkParameter <$> P.fieldWithDefault $$(G.litName "use_spheroid") Nothing (G.VBoolean False) booleanParser) + bqMkCountType :: -- | distinct values Maybe Bool -> diff --git a/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs b/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs index ed3be79037e..e84a06c5df3 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs @@ -28,7 +28,7 @@ instance Backend 'BigQuery where type ScalarType 'BigQuery = BigQuery.ScalarType type SQLExpression 'BigQuery = BigQuery.Expression type SQLOperator 'BigQuery = BigQuery.Op - type BooleanOperators 'BigQuery = Const Void + type BooleanOperators 'BigQuery = BigQuery.BooleanOperators type XComputedField 'BigQuery = XDisable type XRelay 'BigQuery = XDisable diff --git a/server/src-lib/Hasura/Backends/BigQuery/ToQuery.hs b/server/src-lib/Hasura/Backends/BigQuery/ToQuery.hs index 6909caa205b..aab3b13c98d 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/ToQuery.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/ToQuery.hs @@ -105,13 +105,15 @@ fromExpression = "(" <+> fromExpression x <+> ") != (" <+> fromExpression y <+> ")" ToStringExpression e -> "CONCAT(" <+> fromExpression e <+> ", '')" SelectExpression s -> "(" <+> IndentPrinter 1 (fromSelect s) <+> ")" - ListExpression xs -> " UNNEST ([" <+> (SepByPrinter ", " $ fromExpression <$> xs) <+> "])" + ListExpression xs -> " UNNEST ([" <+> SepByPrinter ", " (fromExpression <$> xs) <+> "])" OpExpression op x y -> "(" <+> fromExpression x <+> ") " <+> fromOp op <+> fromExpression y + FunctionExpression name args -> + UnsafeTextPrinter name <+> "(" <+> SepByPrinter ", " (fromExpression <$> args) <+> ")" ConditionalProjection expression fieldName -> "(CASE WHEN(" <+> fromExpression expression <+> ") THEN " @@ -144,6 +146,8 @@ fromOp = LessOrEqualOp -> "<=" InOp -> "IN" NotInOp -> "NOT IN" + LikeOp -> "LIKE" + NotLikeOp -> "NOT LIKE" fromPath :: JsonPath -> Printer fromPath path = diff --git a/server/src-lib/Hasura/Backends/BigQuery/Types.hs b/server/src-lib/Hasura/Backends/BigQuery/Types.hs index f0ed6ba3345..af221aebf10 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/Types.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/Types.hs @@ -7,6 +7,7 @@ module Hasura.Backends.BigQuery.Types ArrayAgg (..), Base64, BigDecimal, + BooleanOperators (..), Cardinality (..), ColumnName (ColumnName), Countable (..), @@ -65,6 +66,7 @@ where import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) import Data.Aeson qualified as J +import Data.Aeson.Extended qualified as J import Data.Aeson.Types qualified as J import Data.ByteString (ByteString) import Data.ByteString.Base64 qualified as Base64 @@ -83,6 +85,7 @@ import Data.Vector.Instances () import Hasura.Base.Error import Hasura.Incremental.Internal.Dependency import Hasura.Prelude +import Hasura.RQL.IR.BoolExp import Hasura.RQL.Types.Common qualified as RQL import Language.GraphQL.Draft.Syntax qualified as G import Language.Haskell.TH.Syntax @@ -402,6 +405,7 @@ data Expression | OpExpression Op Expression Expression | ListExpression [Expression] | CastExpression Expression ScalarType + | FunctionExpression !Text [Expression] | ConditionalProjection Expression FieldName deriving (Eq, Ord, Show, Generic, Data, Lift) @@ -611,9 +615,9 @@ data Op | MoreOrEqualOp | InOp | NotInOp + | LikeOp + | NotLikeOp -- | SNE - -- | SLIKE - -- | SNLIKE -- | SILIKE -- | SNILIKE -- | SSIMILAR @@ -858,6 +862,30 @@ data UnifiedOn = UnifiedOn newtype FunctionName = FunctionName Text -- TODO: Improve this type when SQL function support added deriving (FromJSON, ToJSON, ToJSONKey, ToTxt, Show, Eq, Ord, Hashable, Cacheable, NFData) +data BooleanOperators a + = ASTContains !a + | ASTEquals !a + | ASTTouches !a + | ASTWithin !a + | ASTIntersects !a + | ASTDWithin !(DWithinGeogOp a) + deriving stock (Eq, Generic, Foldable, Functor, Traversable, Show) + +instance NFData a => NFData (BooleanOperators a) + +instance Hashable a => Hashable (BooleanOperators a) + +instance Cacheable a => Cacheable (BooleanOperators a) + +instance ToJSON a => J.ToJSONKeyValue (BooleanOperators a) where + toJSONKeyValue = \case + ASTContains a -> ("_st_contains", J.toJSON a) + ASTEquals a -> ("_st_equals", J.toJSON a) + ASTIntersects a -> ("_st_intersects", J.toJSON a) + ASTTouches a -> ("_st_touches", J.toJSON a) + ASTWithin a -> ("_st_within", J.toJSON a) + ASTDWithin a -> ("_st_dwithin", J.toJSON a) + -------------------------------------------------------------------------------- -- Backend-related stuff -- diff --git a/server/tests-py/queries/graphql_query/boolexp/search/schema_setup_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/search/schema_setup_bigquery.yaml new file mode 100644 index 00000000000..6090718897f --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/search/schema_setup_bigquery.yaml @@ -0,0 +1,18 @@ +type: bulk +args: +- type: bigquery_run_sql + args: + source: bigquery + sql: | + DROP TABLE IF EXISTS `hasura_test.city`; + CREATE TABLE `hasura_test.city` ( + `name` STRING, + `country` STRING + ); + + INSERT INTO `hasura_test.city` (`name`, `country`) + VALUES + ('Durham', 'USA'), + ('New York', 'USA'), + ('Framlingham', 'UK'), + ('New Orleans', 'USA'); diff --git a/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_like_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_like_bigquery.yaml new file mode 100644 index 00000000000..1d39bcc4026 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_like_bigquery.yaml @@ -0,0 +1,21 @@ +description: Select cities ending with ham +url: /v1/graphql +status: 200 +response: + data: + hasura_test_city: + - name: Durham + country: USA + - name: Framlingham + country: UK +query: + query: | + query { + hasura_test_city ( + where: {name: {_like: "%ham" }}, + order_by: {name: asc} + ) { + name + country + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_nlike_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_nlike_bigquery.yaml new file mode 100644 index 00000000000..9bdc753e5a5 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/search/select_city_where_nlike_bigquery.yaml @@ -0,0 +1,21 @@ +description: Select cities not ending with ham +url: /v1/graphql +status: 200 +response: + data: + hasura_test_city: + - name: New Orleans + country: USA + - name: New York + country: USA +query: + query: | + query { + hasura_test_city ( + where: {name: {_nlike: "%ham" }}, + order_by: {name: asc} + ) { + name + country + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/search/setup_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/search/setup_bigquery.yaml new file mode 100644 index 00000000000..cc7cd95241e --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/search/setup_bigquery.yaml @@ -0,0 +1,10 @@ +type: bulk +args: + +#City table +- type: bigquery_track_table + args: + source: bigquery + table: + dataset: hasura_test + name: city diff --git a/server/tests-py/queries/graphql_query/boolexp/search/teardown_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/search/teardown_bigquery.yaml new file mode 100644 index 00000000000..5d65b636173 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/search/teardown_bigquery.yaml @@ -0,0 +1,10 @@ +type: bulk +args: + +- type: bigquery_untrack_table + args: + source: bigquery + table: + dataset: hasura_test + name: city + cascade: true diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_bigquery.yaml new file mode 100644 index 00000000000..09626b56965 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/schema_setup_bigquery.yaml @@ -0,0 +1,45 @@ +type: bulk +args: +- type: bigquery_run_sql + args: + source: bigquery + sql: | + DROP TABLE IF EXISTS `hasura_test.spatial_types_geog`; + CREATE TABLE `hasura_test.spatial_types_geog` ( + `point` GEOGRAPHY, + `linestring` GEOGRAPHY, + `polygon` GEOGRAPHY, + `multipoint` GEOGRAPHY, + `multilinestring` GEOGRAPHY, + `multipolygon` GEOGRAPHY, + `geometrycollection` GEOGRAPHY + ); + INSERT INTO `hasura_test.spatial_types_geog` ( + `point`, + `linestring`, + `polygon`, + `multipoint`, + `multilinestring`, + `multipolygon`, + `geometrycollection` + ) + VALUES ( + st_geogfromtext('POINT(3 4)'), + st_geogfromtext('LINESTRING(1 1,2 3,4 8, -6 3)'), + st_geogfromtext('POLYGON((1 1, 1 2, 2 1, 1 1))'), + st_geogfromtext('MULTIPOINT((2 3), (7 8))'), + st_geogfromtext( + 'MULTILINESTRING((1 1, 3 3, 5 5),(3 3, 5 5, 7 7))' + ), + st_geogfromtext( + -- for some odd reason this multipolygon from 3 polygons goes through a very odd conversion in BigQuery + -- st_geogfromtext('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)))') + -- -> + -- MULTIPOLYGON(((-120.533 46.566, -118.283 46.1, -122.3 47.45, -120.533 46.566)), ((-2 2, -2 -2, 2 -2, 2 1.00015229710421, 3 1, 3 3, 1 3, 1 2.00091355021717, -2 2))) + '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)))' + ), + st_geogfromtext( + 'GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5),POLYGON((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1)))' + ) + ); + diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_bigquery.yaml new file mode 100644 index 00000000000..9bcc2976675 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_bigquery.yaml @@ -0,0 +1,30 @@ +description: GraphQL query to test different data types of SQL Server +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - point: POINT(3 4) + linestring: LINESTRING(1 1, 2 3, 4 8, -6 3) + polygon: POLYGON((2 1, 1 2, 1 1, 2 1)) + multipoint: MULTIPOINT(2 3, 7 8) + multilinestring: + LINESTRING(1 1, 3 3, 5 5, 7 7) + multipolygon: + 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))) + geometrycollection: + GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5), POLYGON((-5 -1, -5 -5, -1 -5, -1 -1, -5 -1))) + +query: + query: | + query { + hasura_test_spatial_types_geog { + point + linestring + polygon + multipoint + multilinestring + multipolygon + geometrycollection + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_bigquery.yaml new file mode 100644 index 00000000000..d1211edc9a1 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_contains_bigquery.yaml @@ -0,0 +1,21 @@ +description: Query data from spatial_types_geo using _st_contains +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - multipolygon: + 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))) +query: + query: | + query { + hasura_test_spatial_types_geog( + where: { + multipolygon: { + _st_contains: "POINT(0.5 0)" + } + } + ) { + multipolygon + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_d_within_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_d_within_bigquery.yaml new file mode 100644 index 00000000000..8dd71476144 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_d_within_bigquery.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geog using _st_d_within +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - multipoint: MULTIPOINT(2 3, 7 8) +query: + query: | + query { + hasura_test_spatial_types_geog( + where: { + multipoint: { + _st_d_within: { distance: 1, from: "POLYGON ((0 0, 10 10, 10 10, 10 0, 0 0))"} + } + } + ) { + multipoint + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_dwithin_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_dwithin_bigquery.yaml new file mode 100644 index 00000000000..8dd71476144 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_dwithin_bigquery.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geog using _st_d_within +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - multipoint: MULTIPOINT(2 3, 7 8) +query: + query: | + query { + hasura_test_spatial_types_geog( + where: { + multipoint: { + _st_d_within: { distance: 1, from: "POLYGON ((0 0, 10 10, 10 10, 10 0, 0 0))"} + } + } + ) { + multipoint + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_bigquery.yaml new file mode 100644 index 00000000000..679e15b8e5c --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_equals_bigquery.yaml @@ -0,0 +1,24 @@ +description: Query data from spatial_types_geog using _st_equals +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - geometrycollection: + GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5), POLYGON((-5 -1, -5 -5, -1 -5, -1 -1, -5 -1))) +query: + # note: using GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5),POLYGON((-1 -1, -1 -5, -5 -5, -5 -1, -1 -1))) + # doesn't work with st_equals in BigQuery even when the docs say that the function should work with + # any point order (probably the problem is in handling GEOGRAPHY params) + query: | + query { + hasura_test_spatial_types_geog( + where: { + geometrycollection: { + _st_equals: "GEOMETRYCOLLECTION(LINESTRING(1 1, 3 5), POLYGON((-5 -1, -5 -5, -1 -5, -1 -1, -5 -1)))" + } + } + ) { + geometrycollection + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_bigquery.yaml new file mode 100644 index 00000000000..29ffa4f34c9 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_intersects_bigquery.yaml @@ -0,0 +1,14 @@ +description: Query data from spatial_types_geog using _st_intersects +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - polygon: POLYGON((2 1, 1 2, 1 1, 2 1)) +query: + query: | + query { + hasura_test_spatial_types_geog(where: { polygon: { _st_intersects: "LINESTRING(0 0, 2 2)" } }) { + polygon + } + } diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_bigquery.yaml new file mode 100644 index 00000000000..d7a731285cb --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_touches_bigquery.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geog using _st_touches +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - point: POINT(3 4) +query: + query: | + query { + hasura_test_spatial_types_geog( + 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_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_bigquery.yaml new file mode 100644 index 00000000000..8dd39c971d8 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/select_query_spatial_types_where_st_within_bigquery.yaml @@ -0,0 +1,20 @@ +description: Query data from spatial_types_geog using _st_within +url: /v1/graphql +status: 200 +response: + data: + hasura_test_spatial_types_geog: + - multipoint: MULTIPOINT(2 3, 7 8) +query: + query: | + query { + hasura_test_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_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/setup_bigquery.yaml new file mode 100644 index 00000000000..233655496ab --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/setup_bigquery.yaml @@ -0,0 +1,9 @@ +type: bulk +args: + +- type: bigquery_track_table + args: + source: bigquery + table: + dataset: hasura_test + name: spatial_types_geog diff --git a/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_bigquery.yaml b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_bigquery.yaml new file mode 100644 index 00000000000..a45bc67e468 --- /dev/null +++ b/server/tests-py/queries/graphql_query/boolexp/spatial/teardown_bigquery.yaml @@ -0,0 +1,10 @@ +type: bulk +args: + +- type: bigquery_untrack_table + args: + source: bigquery + table: + dataset: hasura_test + name: spatial_types_geog + cascade: true diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 86ae80d0730..23f8d7e0b9d 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -223,6 +223,20 @@ class TestGraphQLQueryBasicBigquery: def dir(cls): return 'queries/graphql_query/bigquery' +@pytest.mark.parametrize("transport", ['http', 'websocket']) +@pytest.mark.parametrize("backend", ['bigquery']) +@usefixtures('per_class_tests_db_state') +class TestGraphQLQueryBoolExpSearchBigquery: + + def test_city_where_like(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_city_where_like_bigquery.yaml', transport) + + def test_city_where_not_like(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_city_where_nlike_bigquery.yaml', transport) + + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/search' @pytest.mark.parametrize("transport", ['http', 'websocket']) @pytest.mark.parametrize("backend", ['citus', 'mssql', 'postgres']) @@ -1537,3 +1551,32 @@ class TestGraphQLQueryBoolExpSpatialMSSQL: @classmethod def dir(cls): return 'queries/graphql_query/boolexp/spatial' + +@pytest.mark.parametrize("transport", ['http', 'websocket']) +@pytest.mark.parametrize("backend", ['bigquery']) +@usefixtures('per_class_tests_db_state') +class TestGraphQLQueryBoolExpSpatialBigquery: + def test_select_spatial_bigquery_types(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_equals(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_equals_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_contains(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_contains_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_intersects(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_intersects_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_within(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_within_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_d_within(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_d_within_bigquery.yaml', transport) + + def test_select_spatial_bigquery_types_where_st_touches(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_spatial_types_where_st_touches_bigquery.yaml', transport) + + @classmethod + def dir(cls): + return 'queries/graphql_query/boolexp/spatial'