Logical Models for BigQuery

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8447
Co-authored-by: Daniel Harvey <4729125+danieljharvey@users.noreply.github.com>
Co-authored-by: Nicolas Beaussart <7281023+beaussan@users.noreply.github.com>
Co-authored-by: Antoine Leblanc <1618949+nicuveo@users.noreply.github.com>
Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com>
Co-authored-by: ananya-2410 <107847554+ananya-2410@users.noreply.github.com>
Co-authored-by: Matthew Goodwin <49927862+m4ttheweric@users.noreply.github.com>
Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com>
Co-authored-by: Puru Gupta <32328846+purugupta99@users.noreply.github.com>
Co-authored-by: Gil Mizrahi <8547573+soupi@users.noreply.github.com>
Co-authored-by: Rob Dominguez <24390149+robertjdominguez@users.noreply.github.com>
GitOrigin-RevId: ddef9d54bfad6b7d5dc51251dbe47eac43995da3
This commit is contained in:
Tom Harding 2023-03-24 15:15:24 +00:00 committed by hasura-bot
parent 63eabc2374
commit b2f683f56d
11 changed files with 938 additions and 40 deletions

View File

@ -145,7 +145,7 @@ test-logical-models:
GRAPHQL_ENGINE=$(GRAPHQL_ENGINE_PATH) \ GRAPHQL_ENGINE=$(GRAPHQL_ENGINE_PATH) \
cabal run api-tests:exe:api-tests cabal run api-tests:exe:api-tests
HASURA_TEST_BACKEND_TYPE=BigQuery \ HASURA_TEST_BACKEND_TYPE=BigQuery \
HSPEC_MATCH=Metadata.LogicalModel \ HSPEC_MATCH=LogicalModel \
GRAPHQL_ENGINE=$(GRAPHQL_ENGINE_PATH) \ GRAPHQL_ENGINE=$(GRAPHQL_ENGINE_PATH) \
cabal run api-tests:exe:api-tests cabal run api-tests:exe:api-tests
HASURA_TEST_BACKEND_TYPE=SQLServer \ HASURA_TEST_BACKEND_TYPE=SQLServer \

View File

@ -126,6 +126,7 @@ library
Test.DataConnector.MockAgent.UpdateMutationsSpec Test.DataConnector.MockAgent.UpdateMutationsSpec
Test.DataConnector.QuerySpec Test.DataConnector.QuerySpec
Test.DataConnector.SelectPermissionsSpec Test.DataConnector.SelectPermissionsSpec
Test.Databases.BigQuery.LogicalModelsSpec
Test.Databases.BigQuery.Queries.SpatialTypesSpec Test.Databases.BigQuery.Queries.SpatialTypesSpec
Test.Databases.BigQuery.Queries.TypeInterpretationSpec Test.Databases.BigQuery.Queries.TypeInterpretationSpec
Test.Databases.BigQuery.Schema.ComputedFields.TableSpec Test.Databases.BigQuery.Schema.ComputedFields.TableSpec

View File

@ -0,0 +1,799 @@
{-# LANGUAGE QuasiQuotes #-}
-- | Access to the SQL
module Test.Databases.BigQuery.LogicalModelsSpec (spec) where
import Data.Aeson (Value)
import Data.List.NonEmpty qualified as NE
import Data.String.Interpolate (i)
import Data.Time.Calendar.OrdinalDate
import Data.Time.Clock
import Harness.Backend.BigQuery qualified as BigQuery
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Graphql
import Harness.Quoter.Yaml (interpolateYaml, yaml)
import Harness.Test.BackendType qualified as BackendType
import Harness.Test.Fixture qualified as Fixture
import Harness.Test.Schema (Table (..), table)
import Harness.Test.Schema qualified as Schema
import Harness.Test.SchemaName (unSchemaName)
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment (options), getBackendTypeConfig)
import Harness.Yaml (shouldBeYaml, shouldReturnYaml)
import Hasura.Prelude
import Test.Hspec (SpecWith, describe, it)
-- ** Preamble
featureFlagForLogicalModels :: String
featureFlagForLogicalModels = "HASURA_FF_LOGICAL_MODEL_INTERFACE"
spec :: SpecWith GlobalTestEnvironment
spec =
Fixture.hgeWithEnv [(featureFlagForLogicalModels, "True")] $
Fixture.runClean -- re-run fixture setup on every test
( NE.fromList
[ (Fixture.fixture $ Fixture.Backend BigQuery.backendTypeMetadata)
{ Fixture.setupTeardown = \(testEnvironment, _) ->
[ BigQuery.setupTablesAction schema testEnvironment
]
}
]
)
tests
-- ** Setup and teardown
-- we add and track a table here as it's the only way we can currently define a
-- return type
schema :: [Schema.Table]
schema =
[ (table "article")
{ tableColumns =
[ Schema.column "id" Schema.TInt,
Schema.column "title" Schema.TStr,
Schema.column "content" Schema.TStr,
Schema.column "date" Schema.TUTCTime
],
tableData =
[ [ Schema.VInt 1,
Schema.VStr "Dogs",
Schema.VStr "I like to eat dog food I am a dogs I like to eat dog food I am a dogs I like to eat dog food I am a dogs",
Schema.VUTCTime (UTCTime (fromOrdinalDate 2000 1) 0)
]
]
},
(table "stuff")
{ tableColumns =
[ Schema.column "thing" Schema.TInt,
Schema.column "date" Schema.TUTCTime
]
}
]
tests :: SpecWith TestEnvironment
tests = do
let query :: Text
query = "SELECT * FROM UNNEST([STRUCT('hello' as one, 'world' as two), ('welcome', 'friend')])"
helloWorldLogicalModel :: Schema.LogicalModel
helloWorldLogicalModel =
(Schema.logicalModel "hello_world_function" query)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
describe "Testing Logical Models" $ do
it "Descriptions and nullability appear in the schema" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
sourceName = BackendType.backendSourceName backendTypeMetadata
nullableQuery = "SELECT thing / 2 AS divided, null as something_nullable FROM stuff"
descriptionsAndNullableLogicalModel :: Schema.LogicalModel
descriptionsAndNullableLogicalModel =
(Schema.logicalModel "divided_stuff" nullableQuery)
{ Schema.logicalModelColumns =
[ (Schema.logicalModelColumn "divided" Schema.TInt)
{ Schema.logicalModelColumnDescription = Just "A divided thing"
},
(Schema.logicalModelColumn "something_nullable" Schema.TInt)
{ Schema.logicalModelColumnDescription = Just "Something nullable",
Schema.logicalModelColumnNullable = True
}
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "unused" Schema.TInt
],
Schema.logicalModelReturnTypeDescription = Just "Return type description"
}
Schema.trackLogicalModel sourceName descriptionsAndNullableLogicalModel testEnvironment
let queryTypesIntrospection :: Value
queryTypesIntrospection =
[graphql|
query {
__type(name: "divided_stuff") {
name
description
fields {
name
description
type {
name
kind
ofType {
name
}
}
}
}
}
|]
expected =
[interpolateYaml|
{
"data": {
"__type": {
"description": "Return type description",
"fields": [
{
"description": "A divided thing",
"name": "divided",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"name": "Int"
}
}
},
{
"description": "Something nullable",
"name": "something_nullable",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
],
"name": "divided_stuff"
}
}
}
|]
actual <- GraphqlEngine.postGraphql testEnvironment queryTypesIntrospection
actual `shouldBeYaml` expected
it "Runs the absolute simplest query that takes no parameters" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
Schema.trackLogicalModel source helloWorldLogicalModel testEnvironment
let expected =
[yaml|
data:
hello_world_function:
- one: "hello"
two: "world"
- one: "welcome"
two: "friend"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_world_function {
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Runs simple query with a basic where clause" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
Schema.trackLogicalModel source helloWorldLogicalModel testEnvironment
let expected =
[yaml|
data:
hello_world_function:
- one: "hello"
two: "world"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_world_function (where: { two: { _eq: "world" } }){
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Runs a simple query using distinct_on and order_by" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
queryWithDuplicates :: Text
queryWithDuplicates = "SELECT * FROM UNNEST([STRUCT('hello' as one, 'world' as two), ('hello', 'friend')])"
helloWorldLogicalModelWithDuplicates :: Schema.LogicalModel
helloWorldLogicalModelWithDuplicates =
(Schema.logicalModel "hello_world_function" queryWithDuplicates)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
Schema.trackLogicalModel source helloWorldLogicalModelWithDuplicates testEnvironment
let expected =
[yaml|
data:
hello_world_function:
- one: "hello"
two: "world"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_world_function (
distinct_on: [one]
order_by: [{one:asc}]
){
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Runs a simple query that takes no parameters" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
sourceName = BackendType.backendSourceName backendTypeMetadata
Schema.trackLogicalModel sourceName helloWorldLogicalModel testEnvironment
let expected =
[yaml|
data:
hello_world_function:
- one: "hello"
two: "world"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_world_function(where: {one: {_eq: "hello"}}) {
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Runs a simple query that takes one dummy parameter and order_by" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
helloWorldLogicalModelWithDummyArgument :: Schema.LogicalModel
helloWorldLogicalModelWithDummyArgument =
(Schema.logicalModel "hello_world_function_with_dummy" query)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "dummy" Schema.TStr
]
}
Schema.trackLogicalModel source helloWorldLogicalModelWithDummyArgument testEnvironment
let expected =
[yaml|
data:
hello_world_function_with_dummy:
- two: "world"
- two: "friend"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_world_function_with_dummy(args: {dummy: "ignored"}, order_by: {one: asc}) {
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Runs a simple query that takes no parameters but ends with a comment" $ \testEnvironment -> do
let spicyQuery :: Text
spicyQuery = "SELECT * FROM UNNEST([STRUCT('hello' as one, 'world' as two), ('welcome', 'friend')]) -- my query"
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
helloCommentLogicalModel :: Schema.LogicalModel
helloCommentLogicalModel =
(Schema.logicalModel "hello_comment_function" spicyQuery)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
Schema.trackLogicalModel source helloCommentLogicalModel testEnvironment
let expected =
[yaml|
data:
hello_comment_function:
- one: "hello"
two: "world"
- one: "welcome"
two: "friend"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
hello_comment_function {
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Uses a column permission that we are allowed to access" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
backendType = BackendType.backendTypeString backendTypeMetadata
createPermRequestType = backendType <> "_create_logical_model_select_permission"
helloWorldPermLogicalModel :: Schema.LogicalModel
helloWorldPermLogicalModel =
(Schema.logicalModel "hello_world_perms" query)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
Schema.trackLogicalModel source helloWorldPermLogicalModel testEnvironment
shouldReturnYaml
(options testEnvironment)
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: bulk
args:
- type: *createPermRequestType
args:
source: *source
root_field_name: hello_world_perms
role: "test"
permission:
columns:
- one
filter: {}
|]
)
[yaml|
- message: success
|]
let expected =
[yaml|
data:
hello_world_perms:
- one: "hello"
- one: "welcome"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphqlWithHeaders
testEnvironment
[("X-Hasura-Role", "test")]
[graphql|
query {
hello_world_perms {
one
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Fails because we access a column we do not have permissions for" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
backendType = BackendType.backendTypeString backendTypeMetadata
createPermRequestType = backendType <> "_create_logical_model_select_permission"
helloWorldPermLogicalModel :: Schema.LogicalModel
helloWorldPermLogicalModel =
(Schema.logicalModel "hello_world_perms" query)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
Schema.trackLogicalModel source helloWorldPermLogicalModel testEnvironment
shouldReturnYaml
(options testEnvironment)
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: bulk
args:
- type: *createPermRequestType
args:
source: *source
root_field_name: hello_world_perms
role: "test"
permission:
columns:
- two
filter: {}
|]
)
[yaml|
- message: success
|]
let expected =
[yaml|
errors:
- extensions:
code: validation-failed
path: $.selectionSet.hello_world_perms.selectionSet.one
message: "field 'one' not found in type: 'hello_world_perms'"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphqlWithHeaders
testEnvironment
[("X-Hasura-Role", "test")]
[graphql|
query {
hello_world_perms {
one
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Using row permissions filters out some results" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
backendType = BackendType.backendTypeString backendTypeMetadata
createPermRequestType = backendType <> "_create_logical_model_select_permission"
helloWorldPermLogicalModel :: Schema.LogicalModel
helloWorldPermLogicalModel =
(Schema.logicalModel "hello_world_perms" query)
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "one" Schema.TStr,
Schema.logicalModelColumn "two" Schema.TStr
]
}
Schema.trackLogicalModel source helloWorldPermLogicalModel testEnvironment
shouldReturnYaml
(options testEnvironment)
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: bulk
args:
- type: *createPermRequestType
args:
source: *source
root_field_name: hello_world_perms
role: "test"
permission:
columns: "*"
filter:
one:
_eq: "welcome"
|]
)
[yaml|
- message: success
|]
let expected =
[yaml|
data:
hello_world_perms:
- one: "welcome"
two: "friend"
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphqlWithHeaders
testEnvironment
[("X-Hasura-Role", "test")]
[graphql|
query {
hello_world_perms {
one
two
}
}
|]
shouldReturnYaml (options testEnvironment) actual expected
let articleQuery :: Schema.SchemaName -> Text
articleQuery schemaName =
[i|
SELECT
id,
title,
( substring(content, 1, {{length}}) || (
case when length(content) < {{length}}
then ''
else '...' end
)) as excerpt,
date
from #{unSchemaName schemaName}.article
|]
describe "Testing Logical Models" $ do
it "Runs a simple query that takes one parameter and uses it multiple times" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName :: Schema.SchemaName
schemaName = Schema.getSchemaName testEnvironment
articleWithExcerptLogicalModel :: Schema.LogicalModel
articleWithExcerptLogicalModel =
(Schema.logicalModel "article_with_excerpt" (articleQuery schemaName))
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "id" Schema.TInt,
Schema.logicalModelColumn "title" Schema.TStr,
Schema.logicalModelColumn "excerpt" Schema.TStr,
Schema.logicalModelColumn "date" Schema.TUTCTime
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "length" Schema.TInt
]
}
Schema.trackLogicalModel source articleWithExcerptLogicalModel testEnvironment
let actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
article_with_excerpt(args: { length: 34 }) {
id
title
date
excerpt
}
}
|]
expected =
[yaml|
data:
article_with_excerpt:
- id: '1'
title: "Dogs"
date: "2000-01-01T00:00:00"
excerpt: "I like to eat dog food I am a dogs..."
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Uses two queries with the same argument names and ensure they don't mess with one another" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName :: Schema.SchemaName
schemaName = Schema.getSchemaName testEnvironment
mkArticleWithExcerptLogicalModel :: Text -> Schema.LogicalModel
mkArticleWithExcerptLogicalModel name =
(Schema.logicalModel name (articleQuery schemaName))
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "id" Schema.TInt,
Schema.logicalModelColumn "title" Schema.TStr,
Schema.logicalModelColumn "excerpt" Schema.TStr,
Schema.logicalModelColumn "date" Schema.TUTCTime
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "length" Schema.TInt
]
}
Schema.trackLogicalModel
source
(mkArticleWithExcerptLogicalModel "article_with_excerpt_1")
testEnvironment
Schema.trackLogicalModel
source
(mkArticleWithExcerptLogicalModel "article_with_excerpt_2")
testEnvironment
let actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
article_with_excerpt_1(args: { length: 34 }) {
excerpt
}
article_with_excerpt_2(args: { length: 13 }) {
excerpt
}
}
|]
expected =
[yaml|
data:
article_with_excerpt_1:
- excerpt: "I like to eat dog food I am a dogs..."
article_with_excerpt_2:
- excerpt: "I like to eat..."
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Uses a one parameter query and uses it multiple times" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName :: Schema.SchemaName
schemaName = Schema.getSchemaName testEnvironment
articleWithExcerptLogicalModel :: Schema.LogicalModel
articleWithExcerptLogicalModel =
(Schema.logicalModel "article_with_excerpt" (articleQuery schemaName))
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "id" Schema.TInt,
Schema.logicalModelColumn "title" Schema.TStr,
Schema.logicalModelColumn "excerpt" Schema.TStr,
Schema.logicalModelColumn "date" Schema.TUTCTime
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "length" Schema.TInt
]
}
Schema.trackLogicalModel source articleWithExcerptLogicalModel testEnvironment
let actual :: IO Value
actual =
GraphqlEngine.postGraphql
testEnvironment
[graphql|
query {
first: article_with_excerpt(args: { length: 34 }) {
excerpt
}
second: article_with_excerpt(args: { length: 13 }) {
excerpt
}
}
|]
expected =
[yaml|
data:
first:
- excerpt: "I like to eat dog food I am a dogs..."
second:
- excerpt: "I like to eat..."
|]
shouldReturnYaml (options testEnvironment) actual expected
it "Uses a one parameter query, passing it a GraphQL variable" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName :: Schema.SchemaName
schemaName = Schema.getSchemaName testEnvironment
articleWithExcerptLogicalModel :: Schema.LogicalModel
articleWithExcerptLogicalModel =
(Schema.logicalModel "article_with_excerpt" (articleQuery schemaName))
{ Schema.logicalModelColumns =
[ Schema.logicalModelColumn "id" Schema.TInt,
Schema.logicalModelColumn "title" Schema.TStr,
Schema.logicalModelColumn "excerpt" Schema.TStr,
Schema.logicalModelColumn "date" Schema.TUTCTime
],
Schema.logicalModelArguments =
[ Schema.logicalModelColumn "length" Schema.TInt
]
}
Schema.trackLogicalModel source articleWithExcerptLogicalModel testEnvironment
let variables =
[yaml|
length: 34
|]
actual :: IO Value
actual =
GraphqlEngine.postGraphqlWithVariables
testEnvironment
[graphql|
query MyQuery($length: Int!) {
article_with_excerpt(args: { length: $length }) {
excerpt
}
}
|]
variables
expected =
[yaml|
data:
article_with_excerpt:
- excerpt: "I like to eat dog food I am a dogs..."
|]
shouldReturnYaml (options testEnvironment) actual expected

View File

@ -1,4 +1,5 @@
{-# HLINT ignore "Use onNothing" #-} {-# HLINT ignore "Use onNothing" #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE TemplateHaskellQuotes #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
@ -29,6 +30,7 @@ import Control.DeepSeq (NFData)
import Data.Aeson qualified as J import Data.Aeson qualified as J
import Data.Char qualified as C import Data.Char qualified as C
import Data.Coerce (coerce) import Data.Coerce (coerce)
import Data.Data (Data)
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Data.Text (Text) import Data.Text (Text)
import Data.Text qualified as T import Data.Text qualified as T
@ -42,7 +44,7 @@ import Prelude
-- Defined here and re-exported in the public module to avoid exporting `unName`.` -- Defined here and re-exported in the public module to avoid exporting `unName`.`
newtype Name = Name {unName :: Text} newtype Name = Name {unName :: Text}
deriving stock (Eq, Lift, Ord, Show) deriving stock (Data, Eq, Lift, Ord, Show)
deriving newtype (Semigroup, Hashable, NFData, Pretty, J.ToJSONKey, J.ToJSON) deriving newtype (Semigroup, Hashable, NFData, Pretty, J.ToJSONKey, J.ToJSON)
instance HasCodec Name where instance HasCodec Name where

View File

@ -6,6 +6,7 @@ module Hasura.Backends.BigQuery.FromIr
Error (..), Error (..),
runFromIr, runFromIr,
FromIr, FromIr,
FromIrWriter (..),
FromIrConfig (..), FromIrConfig (..),
defaultFromIrConfig, defaultFromIrConfig,
bigQuerySourceConfigToFromIrConfig, bigQuerySourceConfigToFromIrConfig,
@ -21,9 +22,13 @@ import Data.List.NonEmpty qualified as NE
import Data.Map.Strict (Map) import Data.Map.Strict (Map)
import Data.Map.Strict qualified as M import Data.Map.Strict qualified as M
import Data.Text qualified as T import Data.Text qualified as T
import Data.Text.Extended qualified as T (toTxt)
import Hasura.Backends.BigQuery.Instances.Types () import Hasura.Backends.BigQuery.Instances.Types ()
import Hasura.Backends.BigQuery.Source (BigQuerySourceConfig (..)) import Hasura.Backends.BigQuery.Source (BigQuerySourceConfig (..))
import Hasura.Backends.BigQuery.Types as BigQuery import Hasura.Backends.BigQuery.Types as BigQuery
import Hasura.LogicalModel.IR (LogicalModel (..))
import Hasura.LogicalModel.Metadata (InterpolatedQuery)
import Hasura.LogicalModel.Types (LogicalModelName (..))
import Hasura.Prelude import Hasura.Prelude
import Hasura.RQL.IR qualified as Ir import Hasura.RQL.IR qualified as Ir
import Hasura.RQL.Types.Column qualified as Rql import Hasura.RQL.Types.Column qualified as Rql
@ -100,10 +105,28 @@ instance Show Error where
-- setting the current entity that a given field name refers to. See -- setting the current entity that a given field name refers to. See
-- @fromColumn@. -- @fromColumn@.
newtype FromIr a = FromIr newtype FromIr a = FromIr
{ unFromIr :: ReaderT FromIrReader (StateT FromIrState (Validate (NonEmpty Error))) a { unFromIr ::
ReaderT
FromIrReader
( StateT
FromIrState
( WriterT
FromIrWriter
( Validate (NonEmpty Error)
)
)
)
a
} }
deriving (Functor, Applicative, Monad, MonadValidate (NonEmpty Error)) deriving (Functor, Applicative, Monad, MonadValidate (NonEmpty Error))
-- | Collected from using a logical model in a query.
-- Each entry here because a CTE to be prepended to the query.
newtype FromIrWriter = FromIrWriter
{ fromIrWriterLogicalModels :: Map LogicalModelName (InterpolatedQuery Expression)
}
deriving newtype (Semigroup, Monoid)
data FromIrState = FromIrState data FromIrState = FromIrState
{ indices :: Map Text Int { indices :: Map Text Int
} }
@ -160,11 +183,12 @@ data ParentSelectFromEntity
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Runners -- Runners
runFromIr :: FromIrConfig -> FromIr a -> Validate (NonEmpty Error) a runFromIr :: FromIrConfig -> FromIr a -> Validate (NonEmpty Error) (a, FromIrWriter)
runFromIr config fromIr = runFromIr config fromIr =
evalStateT runWriterT $
(runReaderT (unFromIr fromIr) (FromIrReader {config})) evalStateT
(FromIrState {indices = mempty}) (runReaderT (unFromIr fromIr) (FromIrReader {config}))
(FromIrState {indices = mempty})
bigQuerySourceConfigToFromIrConfig :: BigQuerySourceConfig -> FromIrConfig bigQuerySourceConfigToFromIrConfig :: BigQuerySourceConfig -> FromIrConfig
bigQuerySourceConfigToFromIrConfig BigQuerySourceConfig {_scGlobalSelectLimit} = bigQuerySourceConfigToFromIrConfig BigQuerySourceConfig {_scGlobalSelectLimit} =
@ -237,6 +261,7 @@ fromSelectRows parentSelectFromEntity annSelectG = do
| functionName nm == "unnest" -> fromUnnestedJSON json columns (map fst fields) | functionName nm == "unnest" -> fromUnnestedJSON json columns (map fst fields)
Ir.FromFunction functionName (Rql.FunctionArgsExp positionalArgs namedArgs) Nothing -> Ir.FromFunction functionName (Rql.FunctionArgsExp positionalArgs namedArgs) Nothing ->
fromFunction parentSelectFromEntity functionName positionalArgs namedArgs fromFunction parentSelectFromEntity functionName positionalArgs namedArgs
Ir.FromLogicalModel logicalModel -> fromLogicalModel logicalModel
_ -> refute (pure (FromTypeUnsupported from)) _ -> refute (pure (FromTypeUnsupported from))
Args Args
{ argsOrderBy, { argsOrderBy,
@ -258,7 +283,8 @@ fromSelectRows parentSelectFromEntity annSelectG = do
globalTop <- getGlobalTop globalTop <- getGlobalTop
let select = let select =
Select Select
{ selectCardinality = Many, { selectWith = Nothing,
selectCardinality = Many,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = pure (fieldTextNames fields), selectFinalWantedFields = pure (fieldTextNames fields),
selectGroupBy = mempty, selectGroupBy = mempty,
@ -441,7 +467,8 @@ fromSelectAggregate minnerJoinFields annSelectG = do
} }
pure pure
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = Nothing, selectFinalWantedFields = Nothing,
selectGroupBy = mempty, selectGroupBy = mempty,
@ -452,7 +479,8 @@ fromSelectAggregate minnerJoinFields annSelectG = do
( Aliased ( Aliased
{ aliasedThing = { aliasedThing =
Select Select
{ selectProjections = innerProjections, { selectWith = Nothing,
selectProjections = innerProjections,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFrom, selectFrom,
selectJoins = argsJoins, selectJoins = argsJoins,
@ -611,7 +639,8 @@ unfurlAnnotatedOrderByElement =
{ joinSource = { joinSource =
JoinSelect JoinSelect
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = Nothing, selectFinalWantedFields = Nothing,
selectGroupBy = mempty, selectGroupBy = mempty,
@ -663,7 +692,8 @@ unfurlAnnotatedOrderByElement =
{ joinSource = { joinSource =
JoinSelect JoinSelect
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = Nothing, selectFinalWantedFields = Nothing,
selectTop = NoTop, selectTop = NoTop,
@ -771,6 +801,11 @@ fromAnnBoolExp ::
ReaderT EntityAlias FromIr Expression ReaderT EntityAlias FromIr Expression
fromAnnBoolExp = traverse fromAnnBoolExpFld >=> fromGBoolExp fromAnnBoolExp = traverse fromAnnBoolExpFld >=> fromGBoolExp
fromLogicalModel :: LogicalModel 'BigQuery Expression -> FromIr From
fromLogicalModel LogicalModel {..} = FromIr do
tell (FromIrWriter (M.singleton lmRootFieldName lmInterpolatedQuery))
pure (FromLogicalModel lmRootFieldName)
fromAnnBoolExpFld :: fromAnnBoolExpFld ::
Ir.AnnBoolExpFld 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression Ir.AnnBoolExpFld 'BigQuery Expression -> ReaderT EntityAlias FromIr Expression
fromAnnBoolExpFld = fromAnnBoolExpFld =
@ -787,7 +822,8 @@ fromAnnBoolExpFld =
pure pure
( ExistsExpression ( ExistsExpression
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = Nothing, selectFinalWantedFields = Nothing,
selectGroupBy = mempty, selectGroupBy = mempty,
@ -826,7 +862,8 @@ fromGExists Ir.GExists {_geTable, _geWhere} = do
local (const (fromAlias selectFrom)) (fromGBoolExp _geWhere) local (const (fromAlias selectFrom)) (fromGBoolExp _geWhere)
pure pure
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = Nothing, selectFinalWantedFields = Nothing,
selectGroupBy = mempty, selectGroupBy = mempty,
@ -1219,7 +1256,8 @@ fromObjectRelationSelectG _existingJoins annRelationSelectG = do
joinSource = joinSource =
JoinSelect JoinSelect
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields, selectFinalWantedFields,
selectGroupBy = mempty, selectGroupBy = mempty,
@ -1324,7 +1362,8 @@ fromComputedFieldSelect = \case
wrapUnnest from = wrapUnnest from =
let starSelect = let starSelect =
Select Select
{ selectTop = NoTop, { selectWith = Nothing,
selectTop = NoTop,
selectAsStruct = AsStruct, selectAsStruct = AsStruct,
selectProjections = pure StarProjection, selectProjections = pure StarProjection,
selectFrom = from, selectFrom = from,
@ -1468,7 +1507,8 @@ fromArrayRelationSelectG annRelationSelectG = do
let joinSelect = let joinSelect =
Select Select
{ selectCardinality = One, { selectWith = Nothing,
selectCardinality = One,
selectAsStruct = NoAsStruct, selectAsStruct = NoAsStruct,
selectFinalWantedFields = selectFinalWantedFields select, selectFinalWantedFields = selectFinalWantedFields select,
selectTop = NoTop, selectTop = NoTop,
@ -1498,7 +1538,8 @@ fromArrayRelationSelectG annRelationSelectG = do
{ aliasedAlias = coerce (fromAlias (selectFrom select)), { aliasedAlias = coerce (fromAlias (selectFrom select)),
aliasedThing = aliasedThing =
Select Select
{ selectProjections = { selectWith = Nothing,
selectProjections =
selectProjections select selectProjections select
<> joinFieldProjections <> joinFieldProjections
`appendToNonEmpty` foldMap @Maybe `appendToNonEmpty` foldMap @Maybe
@ -1845,6 +1886,7 @@ fromAlias (FromQualifiedTable Aliased {aliasedAlias}) = EntityAlias aliasedAlias
fromAlias (FromSelect Aliased {aliasedAlias}) = EntityAlias aliasedAlias fromAlias (FromSelect Aliased {aliasedAlias}) = EntityAlias aliasedAlias
fromAlias (FromSelectJson Aliased {aliasedAlias}) = EntityAlias aliasedAlias fromAlias (FromSelectJson Aliased {aliasedAlias}) = EntityAlias aliasedAlias
fromAlias (FromFunction Aliased {aliasedAlias}) = EntityAlias aliasedAlias fromAlias (FromFunction Aliased {aliasedAlias}) = EntityAlias aliasedAlias
fromAlias (FromLogicalModel (LogicalModelName logicalModelName)) = EntityAlias (T.toTxt logicalModelName)
fieldTextNames :: Ir.AnnFieldsG 'BigQuery Void Expression -> [Text] fieldTextNames :: Ir.AnnFieldsG 'BigQuery Void Expression -> [Text]
fieldTextNames = fmap (\(Rql.FieldName name, _) -> name) fieldTextNames = fmap (\(Rql.FieldName name, _) -> name)

View File

@ -33,6 +33,7 @@ import Hasura.GraphQL.Schema.Parser qualified as P
import Hasura.GraphQL.Schema.Select import Hasura.GraphQL.Schema.Select
import Hasura.GraphQL.Schema.Table import Hasura.GraphQL.Schema.Table
import Hasura.GraphQL.Schema.Typename import Hasura.GraphQL.Schema.Typename
import Hasura.LogicalModel.Schema (defaultBuildLogicalModelRootFields)
import Hasura.Name qualified as Name import Hasura.Name qualified as Name
import Hasura.Prelude import Hasura.Prelude
import Hasura.RQL.IR.BoolExp import Hasura.RQL.IR.BoolExp
@ -63,6 +64,7 @@ instance BackendSchema 'BigQuery where
buildFunctionQueryFields _ _ _ _ = pure [] buildFunctionQueryFields _ _ _ _ = pure []
buildFunctionRelayQueryFields _ _ _ _ _ = pure [] buildFunctionRelayQueryFields _ _ _ _ _ = pure []
buildFunctionMutationFields _ _ _ _ = pure [] buildFunctionMutationFields _ _ _ _ = pure []
buildLogicalModelRootFields = defaultBuildLogicalModelRootFields
-- backend extensions -- backend extensions
relayExtension = Nothing relayExtension = Nothing
@ -86,6 +88,10 @@ instance BackendTableSelectSchema 'BigQuery where
selectTableAggregate = defaultSelectTableAggregate selectTableAggregate = defaultSelectTableAggregate
tableSelectionSet = defaultTableSelectionSet tableSelectionSet = defaultTableSelectionSet
instance BackendCustomTypeSelectSchema 'BigQuery where
logicalModelArguments = defaultLogicalModelArgs
logicalModelSelectionSet = defaultLogicalModelSelectionSet
---------------------------------------------------------------- ----------------------------------------------------------------
-- Individual components -- Individual components

View File

@ -1,3 +1,5 @@
{-# LANGUAGE MonadComprehensions #-}
-- | Planning T-SQL queries and subscriptions. -- | Planning T-SQL queries and subscriptions.
module Hasura.Backends.BigQuery.Plan module Hasura.Backends.BigQuery.Plan
( planNoPlan, ( planNoPlan,
@ -6,6 +8,8 @@ where
import Control.Monad.Validate import Control.Monad.Validate
import Data.Aeson.Text import Data.Aeson.Text
import Data.List.NonEmpty qualified as NE
import Data.Map.Strict qualified as Map
import Data.Text.Extended import Data.Text.Extended
import Data.Text.Lazy qualified as LT import Data.Text.Lazy qualified as LT
import Hasura.Backends.BigQuery.FromIr as BigQuery import Hasura.Backends.BigQuery.FromIr as BigQuery
@ -29,8 +33,20 @@ planNoPlan ::
m Select m Select
planNoPlan fromIrConfig userInfo queryDB = do planNoPlan fromIrConfig userInfo queryDB = do
rootField <- traverse (prepareValueNoPlan (_uiSession userInfo)) queryDB rootField <- traverse (prepareValueNoPlan (_uiSession userInfo)) queryDB
runValidate (BigQuery.runFromIr fromIrConfig (BigQuery.fromRootField rootField))
`onLeft` (E.throw400 E.NotSupported . (tshow :: NonEmpty Error -> Text)) (select, FromIrWriter {fromIrWriterLogicalModels}) <-
runValidate (BigQuery.runFromIr fromIrConfig (BigQuery.fromRootField rootField))
`onLeft` (E.throw400 E.NotSupported . (tshow :: NonEmpty Error -> Text))
-- Logical models used within this query need to be converted into CTEs.
-- These need to come before any other CTEs in case those CTEs also depend on
-- the logical models.
let logicalModels :: Maybe With
logicalModels = do
ctes <- NE.nonEmpty (Map.toList fromIrWriterLogicalModels)
pure (With [Aliased query (toTxt name) | (name, query) <- ctes])
pure select {selectWith = logicalModels <> selectWith select}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Resolving values -- Resolving values

View File

@ -25,12 +25,15 @@ import Data.List (intersperse)
import Data.List.NonEmpty qualified as NE import Data.List.NonEmpty qualified as NE
import Data.String import Data.String
import Data.Text qualified as T import Data.Text qualified as T
import Data.Text.Extended qualified as T (toTxt)
import Data.Text.Lazy qualified as LT import Data.Text.Lazy qualified as LT
import Data.Text.Lazy.Builder (Builder) import Data.Text.Lazy.Builder (Builder)
import Data.Text.Lazy.Builder qualified as LT import Data.Text.Lazy.Builder qualified as LT
import Data.Tuple import Data.Tuple
import Data.Vector qualified as V import Data.Vector qualified as V
import Hasura.Backends.BigQuery.Types import Hasura.Backends.BigQuery.Types
import Hasura.LogicalModel.Metadata (InterpolatedItem (..), InterpolatedQuery (..))
import Hasura.LogicalModel.Types (LogicalModelName (..))
import Hasura.Prelude hiding (second) import Hasura.Prelude hiding (second)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -184,10 +187,26 @@ fromSelect Select {..} = finalExpression
fromAsStruct = \case fromAsStruct = \case
AsStruct -> "AS STRUCT" AsStruct -> "AS STRUCT"
NoAsStruct -> "" NoAsStruct -> ""
interpolatedQuery = \case
IIText t -> UnsafeTextPrinter t <+> NewlinePrinter
IIVariable v -> fromExpression v
fromWith = \case
Just (With expressions) -> do
let go :: InterpolatedQuery Expression -> Printer
go = foldr ((<+>) . interpolatedQuery) "" . getInterpolatedQuery
"WITH "
<+> SepByPrinter
("," <+> NewlinePrinter)
[ fromNameText alias <+> " AS " <+> parens (go thing)
| Aliased thing alias <- toList expressions
]
Nothing -> ""
inner = inner =
SepByPrinter SepByPrinter
NewlinePrinter NewlinePrinter
[ "SELECT ", [ fromWith selectWith,
"SELECT ",
fromAsStruct selectAsStruct, fromAsStruct selectAsStruct,
IndentPrinter 7 projections, IndentPrinter 7 projections,
"FROM " <+> IndentPrinter 5 (fromFrom selectFrom), "FROM " <+> IndentPrinter 5 (fromFrom selectFrom),
@ -539,6 +558,8 @@ fromFrom =
) )
selectFromFunction selectFromFunction
) )
FromLogicalModel (LogicalModelName logicalModelName) ->
fromNameText (T.toTxt logicalModelName)
fromTableName :: TableName -> Printer fromTableName :: TableName -> Printer
fromTableName TableName {tableName, tableNameSchema} = fromTableName TableName {tableName, tableNameSchema} =

View File

@ -50,6 +50,7 @@ module Hasura.Backends.BigQuery.Types
Top (..), Top (..),
Value (..), Value (..),
Where (..), Where (..),
With (..),
WindowFunction (..), WindowFunction (..),
aggregateProjectionsFieldOrigin, aggregateProjectionsFieldOrigin,
doubleToBigDecimal, doubleToBigDecimal,
@ -96,6 +97,7 @@ import Data.Vector.Instances ()
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.Base.ErrorValue qualified as ErrorValue import Hasura.Base.ErrorValue qualified as ErrorValue
import Hasura.Base.ToErrorValue import Hasura.Base.ToErrorValue
import Hasura.LogicalModel.Metadata (InterpolatedQuery, LogicalModelName)
import Hasura.Metadata.DTO.Utils (boundedEnumCodec) import Hasura.Metadata.DTO.Utils (boundedEnumCodec)
import Hasura.Prelude hiding (state) import Hasura.Prelude hiding (state)
import Hasura.RQL.IR.BoolExp import Hasura.RQL.IR.BoolExp
@ -105,7 +107,8 @@ import Language.Haskell.TH.Syntax hiding (location)
import Text.ParserCombinators.ReadP (eof, readP_to_S) import Text.ParserCombinators.ReadP (eof, readP_to_S)
data Select = Select data Select = Select
{ selectTop :: Top, { selectWith :: Maybe With,
selectTop :: Top,
selectAsStruct :: AsStruct, selectAsStruct :: AsStruct,
selectProjections :: NonEmpty Projection, selectProjections :: NonEmpty Projection,
selectFrom :: From, selectFrom :: From,
@ -118,7 +121,7 @@ data Select = Select
selectCardinality :: Cardinality selectCardinality :: Cardinality
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
-- | Helper type allowing addition of extra fields used -- | Helper type allowing addition of extra fields used
-- in PARTITION BY. -- in PARTITION BY.
@ -149,14 +152,14 @@ data ArrayAgg = ArrayAgg
arrayAggTop :: Top arrayAggTop :: Top
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data Reselect = Reselect data Reselect = Reselect
{ reselectProjections :: NonEmpty Projection, { reselectProjections :: NonEmpty Projection,
reselectWhere :: Where reselectWhere :: Where
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data OrderBy = OrderBy data OrderBy = OrderBy
{ orderByFieldName :: FieldName, { orderByFieldName :: FieldName,
@ -183,7 +186,7 @@ data FieldOrigin
= NoOrigin = NoOrigin
| AggregateOrigin [Aliased Aggregate] | AggregateOrigin [Aliased Aggregate]
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
aggregateProjectionsFieldOrigin :: Projection -> FieldOrigin aggregateProjectionsFieldOrigin :: Projection -> FieldOrigin
aggregateProjectionsFieldOrigin = \case aggregateProjectionsFieldOrigin = \case
@ -202,7 +205,7 @@ data Projection
| ArrayEntityProjection EntityAlias (Aliased [FieldName]) | ArrayEntityProjection EntityAlias (Aliased [FieldName])
| WindowProjection (Aliased WindowFunction) | WindowProjection (Aliased WindowFunction)
deriving stock (Eq, Show, Generic, Data, Lift, Ord) deriving stock (Eq, Show, Generic, Data, Lift, Ord)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data WindowFunction data WindowFunction
= -- | ROW_NUMBER() OVER(PARTITION BY field) = -- | ROW_NUMBER() OVER(PARTITION BY field)
@ -220,7 +223,7 @@ data Join = Join
joinRightTable :: EntityAlias joinRightTable :: EntityAlias
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data JoinProvenance data JoinProvenance
= OrderByJoinProvenance = OrderByJoinProvenance
@ -229,19 +232,19 @@ data JoinProvenance
| ArrayJoinProvenance [Text] | ArrayJoinProvenance [Text]
| MultiplexProvenance | MultiplexProvenance
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data JoinSource data JoinSource
= JoinSelect Select = JoinSelect Select
-- We're not using existingJoins at the moment, which was used to -- We're not using existingJoins at the moment, which was used to
-- avoid re-joining on the same table twice. -- avoid re-joining on the same table twice.
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
newtype Where newtype Where
= Where [Expression] = Where [Expression]
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving newtype (FromJSON, Hashable, Monoid, NFData, Semigroup) deriving newtype (Hashable, Monoid, NFData, Semigroup)
data Cardinality data Cardinality
= Many = Many
@ -255,6 +258,11 @@ data AsStruct
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData, ToJSON) deriving anyclass (FromJSON, Hashable, NFData, ToJSON)
-- | A Common Table Expression clause.
newtype With = With (NonEmpty (Aliased (InterpolatedQuery Expression)))
deriving stock (Data, Generic, Lift)
deriving newtype (Eq, Hashable, NFData, Ord, Semigroup, Show)
data Top data Top
= NoTop = NoTop
| Top Int.Int64 | Top Int.Int64
@ -300,7 +308,7 @@ data Expression
-- `argument_name` => 'argument_value' -- `argument_name` => 'argument_value'
FunctionNamedArgument Text Expression FunctionNamedArgument Text Expression
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data JsonPath data JsonPath
= RootPath = RootPath
@ -315,7 +323,7 @@ data Aggregate
| OpAggregate Text Expression | OpAggregate Text Expression
| TextAggregate Text | TextAggregate Text
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data Countable fieldname data Countable fieldname
= StarCountable = StarCountable
@ -336,29 +344,30 @@ data From
| FromSelect (Aliased Select) | FromSelect (Aliased Select)
| FromSelectJson (Aliased SelectJson) | FromSelectJson (Aliased SelectJson)
| FromFunction (Aliased SelectFromFunction) | FromFunction (Aliased SelectFromFunction)
| FromLogicalModel LogicalModelName
deriving stock (Eq, Show, Generic, Data, Lift, Ord) deriving stock (Eq, Show, Generic, Data, Lift, Ord)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data SelectJson = SelectJson data SelectJson = SelectJson
{ selectJsonBody :: Expression, { selectJsonBody :: Expression,
selectJsonFields :: [(ColumnName, ScalarType)] selectJsonFields :: [(ColumnName, ScalarType)]
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data SelectFromFunction = SelectFromFunction data SelectFromFunction = SelectFromFunction
{ sffFunctionName :: FunctionName, { sffFunctionName :: FunctionName,
sffArguments :: [Expression] sffArguments :: [Expression]
} }
deriving stock (Eq, Show, Generic, Data, Lift, Ord) deriving stock (Eq, Show, Generic, Data, Lift, Ord)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data OpenJson = OpenJson data OpenJson = OpenJson
{ openJsonExpression :: Expression, { openJsonExpression :: Expression,
openJsonWith :: NonEmpty JsonFieldSpec openJsonWith :: NonEmpty JsonFieldSpec
} }
deriving stock (Eq, Ord, Show, Generic, Data, Lift) deriving stock (Eq, Ord, Show, Generic, Data, Lift)
deriving anyclass (FromJSON, Hashable, NFData) deriving anyclass (Hashable, NFData)
data JsonFieldSpec data JsonFieldSpec
= IntField Text = IntField Text

View File

@ -39,6 +39,7 @@ import Hasura.RQL.Types.Common (SourceName, ToAesonPairs (toAesonPairs), default
import Hasura.RQL.Types.Permission (SelPermDef, _pdRole) import Hasura.RQL.Types.Permission (SelPermDef, _pdRole)
import Hasura.SQL.Backend import Hasura.SQL.Backend
import Hasura.Session (RoleName) import Hasura.Session (RoleName)
import Language.Haskell.TH.Syntax (Lift)
newtype RawQuery = RawQuery {getRawQuery :: Text} newtype RawQuery = RawQuery {getRawQuery :: Text}
deriving newtype (Eq, Ord, Show, FromJSON, ToJSON) deriving newtype (Eq, Ord, Show, FromJSON, ToJSON)
@ -54,7 +55,7 @@ data InterpolatedItem variable
IIText Text IIText Text
| -- | a captured variable | -- | a captured variable
IIVariable variable IIVariable variable
deriving stock (Eq, Ord, Show, Functor, Foldable, Data, Generic, Traversable) deriving stock (Eq, Ord, Show, Functor, Foldable, Data, Generic, Lift, Traversable)
-- | Converting an interpolated query back to text. -- | Converting an interpolated query back to text.
-- Should roundtrip with the 'parseInterpolatedQuery'. -- Should roundtrip with the 'parseInterpolatedQuery'.
@ -74,7 +75,7 @@ newtype InterpolatedQuery variable = InterpolatedQuery
{ getInterpolatedQuery :: [InterpolatedItem variable] { getInterpolatedQuery :: [InterpolatedItem variable]
} }
deriving newtype (Eq, Ord, Show, Generic) deriving newtype (Eq, Ord, Show, Generic)
deriving stock (Data, Functor, Foldable, Traversable) deriving stock (Data, Functor, Foldable, Lift, Traversable)
deriving newtype instance (Hashable variable) => Hashable (InterpolatedQuery variable) deriving newtype instance (Hashable variable) => Hashable (InterpolatedQuery variable)

View File

@ -15,11 +15,12 @@ import Hasura.Metadata.DTO.Utils (codecNamePrefix)
import Hasura.Prelude hiding (first) import Hasura.Prelude hiding (first)
import Hasura.RQL.Types.Backend (Backend (..)) import Hasura.RQL.Types.Backend (Backend (..))
import Language.GraphQL.Draft.Syntax qualified as G import Language.GraphQL.Draft.Syntax qualified as G
import Language.Haskell.TH.Syntax (Lift)
-- The name of a logical model. This appears as a root field name in the graphql schema. -- The name of a logical model. This appears as a root field name in the graphql schema.
newtype LogicalModelName = LogicalModelName {getLogicalModelName :: G.Name} newtype LogicalModelName = LogicalModelName {getLogicalModelName :: G.Name}
deriving newtype (Eq, Ord, Show, Hashable, NFData, ToJSON, FromJSON, ToTxt) deriving newtype (Eq, Ord, Show, Hashable, NFData, ToJSON, FromJSON, ToTxt)
deriving stock (Generic) deriving stock (Data, Generic, Lift)
instance HasCodec LogicalModelName where instance HasCodec LogicalModelName where
codec = dimapCodec LogicalModelName getLogicalModelName codec codec = dimapCodec LogicalModelName getLogicalModelName codec