Skip to content

Commit

Permalink
Add support for ilike and nilike operators in BigQurey
Browse files Browse the repository at this point in the history
PR-URL: hasura/graphql-engine-mono#10638
GitOrigin-RevId: 6e97d480282b19ef0c87e08719e6421c09665d15
  • Loading branch information
i-am-tom authored and hasura-bot committed Jan 23, 2024
1 parent d177c6f commit cc588a1
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 9 deletions.
1 change: 1 addition & 0 deletions server/lib/api-tests/api-tests.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ library
Test.DataConnector.QuerySpec
Test.DataConnector.SelectPermissionsSpec
Test.Databases.BigQuery.Queries.SpatialTypesSpec
Test.Databases.BigQuery.Queries.TextFunctionsSpec
Test.Databases.BigQuery.Queries.TypeInterpretationSpec
Test.Databases.BigQuery.Schema.ComputedFields.TableSpec
Test.Databases.Postgres.ArraySpec
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
{-# LANGUAGE QuasiQuotes #-}

-- | Test text search functions in BigQuery
module Test.Databases.BigQuery.Queries.TextFunctionsSpec (spec) where

import Data.Aeson (Value)
import Data.List.NonEmpty qualified as NE
import Harness.Backend.BigQuery qualified as BigQuery
import Harness.GraphqlEngine (postGraphql)
import Harness.Quoter.Graphql (graphql)
import Harness.Quoter.Yaml (interpolateYaml)
import Harness.Schema qualified as Schema
import Harness.Test.Fixture qualified as Fixture
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment (..))
import Harness.Yaml (shouldReturnYaml)
import Hasura.Prelude
import Test.Hspec (SpecWith, describe, it)

spec :: SpecWith GlobalTestEnvironment
spec =
Fixture.run
( NE.fromList
[ (Fixture.fixture $ Fixture.Backend BigQuery.backendTypeMetadata)
{ Fixture.setupTeardown = \(testEnvironment, _) ->
[ BigQuery.setupTablesAction schema testEnvironment
]
}
]
)
tests

--------------------------------------------------------------------------------
-- Schema

schema :: [Schema.Table]
schema =
[ (Schema.table "languages")
{ Schema.tableColumns =
[ Schema.column "name" Schema.TStr
],
Schema.tablePrimaryKey = [],
Schema.tableData =
[ [Schema.VStr "Python"],
[Schema.VStr "C"],
[Schema.VStr "C++"],
[Schema.VStr "Java"],
[Schema.VStr "C#"],
[Schema.VStr "JavaScript"],
[Schema.VStr "PHP"],
[Schema.VStr "Visual Basic"],
[Schema.VStr "SQL"],
[Schema.VStr "Scratch"],
[Schema.VStr "Go"],
[Schema.VStr "Fortran"],
[Schema.VStr "Delphi"],
[Schema.VStr "MATLAB"],
[Schema.VStr "Assembly"],
[Schema.VStr "Swift"],
[Schema.VStr "Kotlin"],
[Schema.VStr "Ruby"],
[Schema.VStr "Rust"],
[Schema.VStr "COBOL"]
]
}
]

--------------------------------------------------------------------------------
-- Tests

tests :: SpecWith TestEnvironment
tests = do
describe "Text predicates" do
it "ilike" \testEnvironment -> do
let schemaName :: Schema.SchemaName
schemaName = Schema.getSchemaName testEnvironment

let expected :: Value
expected =
[interpolateYaml|
data:
#{schemaName}_languages:
- name: Assembly
- name: Fortran
- name: Java
- name: JavaScript
- name: MATLAB
- name: Scratch
- name: Visual Basic
|]

actual :: IO Value
actual =
postGraphql
testEnvironment
[graphql|
query {
#{schemaName}_languages (
order_by: { name: asc },
where: { name: { _ilike: "%a%" } }
) {
name
}
}
|]

shouldReturnYaml testEnvironment actual expected

it "like" \testEnvironment -> do
let schemaName = Schema.getSchemaName testEnvironment
let expected :: Value
expected =
[interpolateYaml|
data:
#{schemaName}_languages:
- name: Fortran
- name: Java
- name: JavaScript
- name: Scratch
- name: Visual Basic
|]

actual :: IO Value
actual =
postGraphql
testEnvironment
[graphql|
query {
#{schemaName}_languages (
order_by: { name: asc },
where: { name: { _like: "%a%" } }
) {
name
}
}
|]

shouldReturnYaml testEnvironment actual expected

it "nlike" \testEnvironment -> do
let schemaName = Schema.getSchemaName testEnvironment
let expected :: Value
expected =
[interpolateYaml|
data:
#{schemaName}_languages:
- name: Assembly
- name: C
- name: C#
- name: C++
- name: COBOL
- name: Delphi
- name: Go
- name: Kotlin
- name: MATLAB
- name: PHP
- name: Python
- name: Ruby
- name: Rust
- name: SQL
- name: Swift
|]

actual :: IO Value
actual =
postGraphql
testEnvironment
[graphql|
query {
#{schemaName}_languages (
order_by: { name: asc },
where: { name: { _nlike: "%a%" } }
) {
name
}
}
|]

shouldReturnYaml testEnvironment actual expected

it "nilike" \testEnvironment -> do
let schemaName = Schema.getSchemaName testEnvironment
let expected :: Value
expected =
[interpolateYaml|
data:
#{schemaName}_languages:
- name: C
- name: C#
- name: C++
- name: COBOL
- name: Delphi
- name: Go
- name: Kotlin
- name: PHP
- name: Python
- name: Ruby
- name: Rust
- name: SQL
- name: Swift
|]

actual :: IO Value
actual =
postGraphql
testEnvironment
[graphql|
query {
#{schemaName}_languages (
order_by: { name: asc },
where: { name: { _nilike: "%a%" } }
) {
name
}
}
|]

shouldReturnYaml testEnvironment actual expected
16 changes: 11 additions & 5 deletions server/src-lib/Hasura/Backends/BigQuery/DDL/BoolExp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Data.Aeson qualified as J
import Data.Aeson.Key qualified as K
import Data.Aeson.KeyMap qualified as KM
import Data.Text.Extended
import Hasura.Backends.BigQuery.Types (ScalarType (StringScalarType))
import Hasura.Backends.BigQuery.Types (BooleanOperators (..), ScalarType (StringScalarType))
import Hasura.Base.Error
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp
Expand Down Expand Up @@ -50,10 +50,14 @@ parseBoolExpOperations rhsParser _rootTableFieldInfoMap _fields columnRef value
"$gte" -> parseGte
"_lte" -> parseLte
"$lte" -> parseLte
"_like" -> parseLike
"$like" -> parseLike
"_nlike" -> parseNlike
"$nlike" -> parseNlike
"_like" -> parseLike
"$nlike" -> parseNLike
"_nlike" -> parseNLike
"$ilike" -> parseILike
"_ilike" -> parseILike
"$nilike" -> parseNILike
"_nilike" -> parseNILike
"_in" -> parseIn
"$in" -> parseIn
"_nin" -> parseNin
Expand All @@ -75,7 +79,9 @@ parseBoolExpOperations rhsParser _rootTableFieldInfoMap _fields columnRef value
parseGte = AGTE <$> parseOne
parseLte = ALTE <$> parseOne
parseLike = guardType StringScalarType >> ALIKE <$> parseOne
parseNlike = guardType StringScalarType >> ANLIKE <$> parseOne
parseILike = guardType StringScalarType >> ABackendSpecific . ASTILike <$> parseOne
parseNLike = guardType StringScalarType >> ANLIKE <$> parseOne
parseNILike = guardType StringScalarType >> ABackendSpecific . ASTNILike <$> parseOne
parseIn = AIN <$> parseManyWithType colTy
parseNin = ANIN <$> parseManyWithType colTy
parseIsNull = bool ANISNOTNULL ANISNULL <$> decodeValue val
Expand Down
4 changes: 4 additions & 0 deletions server/src-lib/Hasura/Backends/BigQuery/FromIr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,10 @@ fromBackendSpecificOpExpG expression op =
BigQuery.ASTIntersects v -> func "ST_INTERSECTS" v
BigQuery.ASTDWithin (Ir.DWithinGeogOp r v sph) ->
FunctionExpression (FunctionName "ST_DWITHIN" Nothing) [expression, v, r, sph]
BigQuery.ASTILike v ->
OpExpression ILikeOp (FunctionExpression (FunctionName "LOWER" Nothing) [expression]) v
BigQuery.ASTNILike v ->
OpExpression NotILikeOp (FunctionExpression (FunctionName "LOWER" Nothing) [expression]) v

nullableBoolEquality :: Expression -> Expression -> Expression
nullableBoolEquality x y =
Expand Down
14 changes: 13 additions & 1 deletion server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,19 @@ bqComparisonExps = P.memoize 'comparisonExps $ \columnType -> do
collapseIfNull
(C.fromAutogeneratedName Name.__nlike)
(Just "does the column NOT match the given pattern")
(ANLIKE . IR.mkParameter <$> typedParser)
(ANLIKE . IR.mkParameter <$> typedParser),
mkBoolOperator
tCase
collapseIfNull
(C.fromAutogeneratedName Name.__ilike)
(Just "does the column match the given case-insensitive pattern")
(ABackendSpecific . BigQuery.ASTILike . IR.mkParameter <$> typedParser),
mkBoolOperator
tCase
collapseIfNull
(C.fromAutogeneratedName Name.__nilike)
(Just "does the column NOT match the given case-insensitive pattern")
(ABackendSpecific . BigQuery.ASTNILike . IR.mkParameter <$> typedParser)
],
-- Ops for Bytes type
guard (isScalarColumnWhere (== BigQuery.BytesScalarType) columnType)
Expand Down
5 changes: 5 additions & 0 deletions server/src-lib/Hasura/Backends/BigQuery/ToQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ fromOp =
NotInOp -> "NOT IN"
LikeOp -> "LIKE"
NotLikeOp -> "NOT LIKE"
-- BigQuery doesn't have case-insensitive versions of this operator, but
-- that's ok: by this point, we'll have built a version of the query that
-- works case insensitively.
ILikeOp -> "LIKE"
NotILikeOp -> "NOT LIKE"

fromPath :: JsonPath -> Printer
fromPath path =
Expand Down
10 changes: 7 additions & 3 deletions server/src-lib/Hasura/Backends/BigQuery/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -501,9 +501,9 @@ data Op
| NotInOp
| LikeOp
| NotLikeOp
-- | SNE
-- | SILIKE
-- | SNILIKE
| -- | SNE
ILikeOp
| NotILikeOp
-- | SSIMILAR
-- | SNSIMILAR
-- | SGTE
Expand Down Expand Up @@ -790,6 +790,8 @@ data BooleanOperators a
| ASTWithin a
| ASTIntersects a
| ASTDWithin (DWithinGeogOp a)
| ASTILike a
| ASTNILike a
deriving stock (Eq, Generic, Foldable, Functor, Traversable, Show)

instance (NFData a) => NFData (BooleanOperators a)
Expand All @@ -804,6 +806,8 @@ instance (ToJSON a) => J.ToJSONKeyValue (BooleanOperators a) where
ASTTouches a -> ("_st_touches", J.toJSON a)
ASTWithin a -> ("_st_within", J.toJSON a)
ASTDWithin a -> ("_st_dwithin", J.toJSON a)
ASTILike a -> ("_st_ilike", J.toJSON a)
ASTNILike a -> ("_st_nilike", J.toJSON a)

data FunctionName = FunctionName
{ functionName :: Text,
Expand Down

0 comments on commit cc588a1

Please sign in to comment.