[server] Custom types prototype

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7807
Co-authored-by: Tom Harding <6302310+i-am-tom@users.noreply.github.com>
GitOrigin-RevId: ee3c644b92aa71a236d247a0cfc5deb7846f91c2
This commit is contained in:
Daniel Harvey 2023-02-15 17:55:06 +00:00 committed by hasura-bot
parent 96856f5de3
commit 3b42e704dd
18 changed files with 725 additions and 178 deletions

View File

@ -513,6 +513,7 @@ library
, GHC.AssertNF.CPP
, GHC.Stats.Extended
, GHC.Generics.Extended
, Hasura.App
, Hasura.Metadata.Class
@ -718,6 +719,9 @@ library
, Hasura.Server.Version
, Hasura.ShutdownLatch
, Hasura.CustomReturnType
, Hasura.CustomReturnType.Common
, Hasura.EncJSON
, Hasura.GraphQL.Execute.Query
, Hasura.GraphQL.Logging

View File

@ -8,7 +8,6 @@ import Harness.Backend.Postgres qualified as Postgres
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Yaml (yaml)
import Harness.Test.Fixture qualified as Fixture
import Harness.Test.Schema (Table (..), table)
import Harness.Test.Schema qualified as Schema
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldReturnYaml)
@ -39,17 +38,15 @@ spec =
-- ** 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 "already_tracked_return_type")
{ tableColumns =
[ (Schema.table "already_tracked_return_type")
{ Schema.tableColumns =
[ Schema.column "divided" Schema.TInt
]
},
(table "stuff")
{ tableColumns =
(Schema.table "stuff")
{ Schema.tableColumns =
[ Schema.column "thing" Schema.TInt,
Schema.column "date" Schema.TUTCTime
]
@ -66,7 +63,6 @@ tests opts = do
it "Fails to track a Native Query without admin access" $
\testEnv -> do
let schemaName = Schema.getSchemaName testEnv
shouldReturnYaml
opts
( GraphqlEngine.postMetadataWithStatusAndHeaders
@ -85,8 +81,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|
@ -140,8 +136,6 @@ tests opts = do
describe "Implementation" $ do
it "Adds a simple native access function with no arguments and returns a 200" $ \testEnv -> do
let schemaName = Schema.getSchemaName testEnv
shouldReturnYaml
opts
( GraphqlEngine.postMetadata
@ -156,8 +150,8 @@ tests opts = do
arguments:
unused: int
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|
@ -165,8 +159,6 @@ tests opts = do
|]
it "Adding a native access function with broken SQL returns a 400" $ \testEnv -> do
let schemaName = Schema.getSchemaName testEnv
shouldReturnYaml
opts
( GraphqlEngine.postMetadataWithStatus
@ -183,8 +175,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|
@ -194,8 +186,6 @@ tests opts = do
|]
it "Checks for the native access function" $ \testEnv -> do
let schemaName = Schema.getSchemaName testEnv
shouldReturnYaml
opts
( GraphqlEngine.postMetadata
@ -211,8 +201,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|
@ -236,13 +226,11 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: hasura
columns:
divided: integer
|]
it "Drops a native access function and returns a 200" $ \testEnv -> do
let schemaName = Schema.getSchemaName testEnv
_ <-
GraphqlEngine.postMetadata
testEnv
@ -257,8 +245,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
shouldReturnYaml
@ -277,8 +265,6 @@ tests opts = do
|]
it "Checks the native access function can be deleted" $ \testEnv -> do
let schemaName = Schema.getSchemaName testEnv
_ <-
GraphqlEngine.postMetadata
testEnv
@ -293,8 +279,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
_ <-
@ -345,7 +331,6 @@ tests opts = do
describe "Validation fails on track a native query when query" do
it "has a syntax error" $
\testEnv -> do
let schemaName = Schema.getSchemaName testEnv
let spicyQuery :: Text
spicyQuery = "query bad"
shouldReturnYaml
@ -364,8 +349,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|
@ -386,7 +371,6 @@ tests opts = do
it "refers to non existing table" $
\testEnv -> do
let schemaName = Schema.getSchemaName testEnv
let spicyQuery :: Text
spicyQuery = "SELECT thing / {{denominator}} AS divided FROM does_not_exist WHERE date = {{target_date}}"
shouldReturnYaml
@ -405,8 +389,8 @@ tests opts = do
denominator: int
target_date: date
returns:
name: already_tracked_return_type
schema: *schemaName
columns:
divided: integer
|]
)
[yaml|

View File

@ -46,13 +46,7 @@ spec =
-- return type
schema :: [Schema.Table]
schema =
[ (table "hello_world_table")
{ tableColumns =
[ Schema.column "one" Schema.TStr,
Schema.column "two" Schema.TStr
]
},
(table "article")
[ (table "article")
{ tableColumns =
[ Schema.column "id" Schema.TInt,
Schema.column "title" Schema.TStr,
@ -66,14 +60,6 @@ schema =
Schema.VUTCTime (UTCTime (fromOrdinalDate 2000 1) 0)
]
]
},
(table "article_excerpt")
{ tableColumns =
[ Schema.column "id" Schema.TInt,
Schema.column "title" Schema.TStr,
Schema.column "excerpt" Schema.TStr,
Schema.column "date" Schema.TUTCTime
]
}
]
@ -86,10 +72,9 @@ tests opts = do
shouldBe = shouldReturnYaml opts
describe "Testing Native Access" $ do
it "Runs a simple query that takes no parameters" $ \testEnvironment -> do
it "Runs the absolute simplest query that takes no parameters" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName = Schema.getSchemaName testEnvironment
shouldReturnYaml
opts
@ -103,8 +88,161 @@ tests opts = do
root_field_name: hello_world_function
code: *query
returns:
name: hello_world_table
schema: *schemaName
columns:
one: text
two: text
|]
)
[yaml|
message: success
|]
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
}
}
|]
actual `shouldBe` expected
it "Runs simple query with a basic where clause" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
shouldReturnYaml
opts
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: pg_track_native_query
args:
type: query
source: *source
root_field_name: hello_world_function
code: *query
returns:
columns:
one: text
two: text
|]
)
[yaml|
message: success
|]
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
}
}
|]
actual `shouldBe` 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 (VALUES ('hello', 'world'), ('hello', 'friend')) as t(\"one\", \"two\")"
shouldReturnYaml
opts
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: pg_track_native_query
args:
type: query
source: *source
root_field_name: hello_world_function
code: *queryWithDuplicates
returns:
columns:
one: text
two: text
|]
)
[yaml|
message: success
|]
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
}
}
|]
actual `shouldBe` expected
it "Runs a simple query that takes no parameters" $ \testEnvironment -> do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
shouldReturnYaml
opts
( GraphqlEngine.postMetadata
testEnvironment
[yaml|
type: pg_track_native_query
args:
type: query
source: *source
root_field_name: hello_world_function
code: *query
returns:
columns:
one: text
two: text
|]
)
[yaml|
@ -134,10 +272,9 @@ tests opts = do
actual `shouldBe` expected
it "Runs a simple query that takes one dummy parameter" $ \testEnvironment -> do
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
schemaName = Schema.getSchemaName testEnvironment
shouldReturnYaml
opts
@ -153,8 +290,9 @@ tests opts = do
dummy: varchar
code: *query
returns:
name: hello_world_table
schema: *schemaName
columns:
one: text
two: text
|]
)
[yaml|
@ -189,7 +327,6 @@ tests opts = do
let backendTypeMetadata = fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnvironment
source = BackendType.backendSourceName backendTypeMetadata
schemaName = Schema.getSchemaName testEnvironment
shouldReturnYaml
opts
@ -203,8 +340,9 @@ tests opts = do
root_field_name: hello_comment_function
code: *spicyQuery
returns:
name: hello_world_table
schema: *schemaName
columns:
one: text
two: text
|]
)
[yaml|
@ -239,7 +377,6 @@ tests opts = 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.getSchemaName testEnvironment
let spicyQuery :: Text
spicyQuery =
@ -265,8 +402,11 @@ tests opts = do
arguments:
length: int
returns:
name: article_excerpt
schema: *schemaName
columns:
id: integer
title: text
excerpt: text
date: date
|]
)
[yaml|
@ -303,7 +443,6 @@ tests opts = do
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.getSchemaName testEnvironment
let spicyQuery :: Text
spicyQuery =
@ -329,8 +468,11 @@ tests opts = do
arguments:
length: int
returns:
name: article_excerpt
schema: *schemaName
columns:
id: integer
title: text
excerpt: text
date: date
|]
)
[yaml|
@ -351,8 +493,11 @@ tests opts = do
arguments:
length: int
returns:
name: article_excerpt
schema: *schemaName
columns:
id: integer
title: text
excerpt: text
date: date
|]
)
[yaml|
@ -388,7 +533,6 @@ tests opts = do
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.getSchemaName testEnvironment
let spicyQuery :: Text
spicyQuery =
@ -414,8 +558,11 @@ tests opts = do
arguments:
length: int
returns:
name: article_excerpt
schema: *schemaName
columns:
id: integer
title: text
excerpt: text
date: date
|]
)
[yaml|
@ -451,7 +598,6 @@ tests opts = do
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.getSchemaName testEnvironment
let spicyQuery :: Text
spicyQuery =
@ -477,8 +623,11 @@ tests opts = do
arguments:
length: int
returns:
name: article_excerpt
schema: *schemaName
columns:
id: integer
title: text
excerpt: text
date: date
|]
)
[yaml|

View File

@ -94,7 +94,7 @@ ifMatchedObjectParser tableInfo = runMaybeT do
matchColumnsName = Name._match_columns
updateColumnsName = Name._update_columns
whereName = Name._where
whereExpParser <- boolExp tableInfo
whereExpParser <- tableBoolExp tableInfo
pure $
P.object objectName (Just objectDesc) do
_imConditions <-

View File

@ -282,6 +282,15 @@ instance
selectTableAggregate = defaultSelectTableAggregate
tableSelectionSet = defaultTableSelectionSet
instance
( PostgresSchema pgKind,
Backend ('Postgres pgKind)
) =>
BS.BackendCustomTypeSelectSchema ('Postgres pgKind)
where
customTypeArguments = defaultCustomTypeArgs
customTypeSelectionSet = defaultCustomTypeSelectionSet
instance
( Backend ('Postgres pgKind),
PostgresSchema pgKind

View File

@ -91,7 +91,7 @@ conflictObjectParser tableInfo maybeUpdatePerms constraints = do
mkTypename = runMkTypename $ _rscTypeNames customization
updateColumnsEnum <- updateColumnsPlaceholderParser tableInfo
constraintParser <- conflictConstraint constraints tableInfo
whereExpParser <- boolExp tableInfo
whereExpParser <- tableBoolExp tableInfo
tableGQLName <- getTableIdentifierName tableInfo
let objectName = mkTypename $ applyTypeNameCaseIdentifier tCase $ mkOnConflictTypeName tableGQLName
objectDesc = G.Description $ "on_conflict condition type for table " <>> tableName

View File

@ -0,0 +1,37 @@
{-# LANGUAGE UndecidableInstances #-}
module Hasura.CustomReturnType
( CustomReturnType (..),
)
where
import Autodocodec (HasCodec, requiredField)
import Autodocodec qualified as AC
import Hasura.Metadata.DTO.Utils (codecNamePrefix)
import Hasura.Prelude hiding (first)
import Hasura.RQL.Types.Backend (Backend (..))
import Hasura.SQL.Backend (BackendType)
-- | Description of a custom return type for a Native Query
newtype CustomReturnType (b :: BackendType) = CustomReturnType
{ crtColumns :: HashMap (Column b) (ScalarType b)
}
instance (Backend b) => HasCodec (CustomReturnType b) where
codec =
AC.CommentCodec
("A return type for a native query.")
$ AC.object (codecNamePrefix @b <> "CustomReturnType")
$ CustomReturnType
<$> requiredField "columns" columnsDoc
AC..= crtColumns
where
columnsDoc = "Return types for the native query"
deriving stock instance (Backend b) => Eq (CustomReturnType b)
deriving stock instance (Backend b) => Show (CustomReturnType b)
deriving newtype instance (Backend b) => Hashable (CustomReturnType b)
deriving newtype instance (Backend b) => NFData (CustomReturnType b)

View File

@ -0,0 +1,36 @@
module Hasura.CustomReturnType.Common
( toFieldInfo,
)
where
import Data.HashMap.Strict qualified as HashMap
import Data.Text.Extended (ToTxt (toTxt))
import Hasura.CustomReturnType (CustomReturnType (..))
import Hasura.Prelude
import Hasura.RQL.Types.Backend (Backend (..))
import Hasura.RQL.Types.Column (ColumnInfo (..), ColumnMutability (..), ColumnType (..))
import Hasura.RQL.Types.Table (FieldInfo (..))
import Language.GraphQL.Draft.Syntax qualified as G
toFieldInfo :: forall b. (Backend b) => CustomReturnType b -> Maybe [FieldInfo b]
toFieldInfo customReturnType =
traverseWithIndex
(\i -> fmap FIColumn . customTypeToColumnInfo i)
(HashMap.toList (crtColumns customReturnType))
where
traverseWithIndex :: (Applicative m) => (Int -> aa -> m bb) -> [aa] -> m [bb]
traverseWithIndex f = zipWithM f [0 ..]
customTypeToColumnInfo :: Int -> (Column b, ScalarType b) -> Maybe (ColumnInfo b)
customTypeToColumnInfo i (column, scalarType) = do
name <- G.mkName (toTxt column)
pure $
ColumnInfo
{ ciColumn = column,
ciName = name,
ciPosition = i,
ciType = ColumnScalar scalarType,
ciIsNullable = False,
ciDescription = Nothing,
ciMutability = ColumnMutability {_cmIsInsertable = False, _cmIsUpdatable = False}
}

View File

@ -26,6 +26,7 @@ module Hasura.GraphQL.Schema.Backend
( -- * Main Types
BackendSchema (..),
BackendTableSelectSchema (..),
BackendCustomTypeSelectSchema (..),
BackendUpdateOperatorsSchema (..),
MonadBuildSchema,
@ -39,6 +40,7 @@ where
import Data.Kind (Type)
import Data.Text.Casing (GQLNameIdentifier)
import Hasura.CustomReturnType (CustomReturnType)
import Hasura.GraphQL.ApolloFederation (ApolloFederationParserFunction)
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.NamingCase
@ -300,6 +302,19 @@ class Backend b => BackendTableSelectSchema (b :: BackendType) where
type ComparisonExp b = OpExpG b (UnpreparedValue b)
class Backend b => BackendCustomTypeSelectSchema (b :: BackendType) where
customTypeArguments ::
MonadBuildSourceSchema b r m n =>
G.Name ->
CustomReturnType b ->
SchemaT r m (InputFieldsParser n (IR.SelectArgsG b (UnpreparedValue b)))
customTypeSelectionSet ::
MonadBuildSourceSchema b r m n =>
G.Name ->
CustomReturnType b ->
SchemaT r m (Maybe (Parser 'Output n (AnnotatedFields b)))
class Backend b => BackendUpdateOperatorsSchema (b :: BackendType) where
-- | Intermediate Representation of the set of update operators that act
-- upon table fields during an update mutation. (For example, _set and _inc)

View File

@ -3,7 +3,8 @@
module Hasura.GraphQL.Schema.BoolExp
( AggregationPredicatesSchema (..),
boolExp,
tableBoolExp,
customTypeBoolExp,
mkBoolOperator,
equalityOperators,
comparisonOperators,
@ -14,6 +15,9 @@ import Data.Has (getter)
import Data.Text.Casing (GQLNameIdentifier)
import Data.Text.Casing qualified as C
import Data.Text.Extended
import Hasura.Base.Error (throw500)
import Hasura.CustomReturnType (CustomReturnType)
import Hasura.CustomReturnType.Common
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Common
@ -42,6 +46,7 @@ import Hasura.RQL.Types.SourceCustomization
import Hasura.RQL.Types.Table
import Hasura.SQL.Backend (BackendType)
import Language.GraphQL.Draft.Syntax qualified as G
import Type.Reflection
-- | Backends implement this type class to specify the schema of
-- aggregation predicates.
@ -75,30 +80,33 @@ instance {-# OVERLAPPABLE #-} (AggregationPredicates b ~ Const Void) => Aggregat
-- > column: type_comparison_exp
-- > ...
-- > }
boolExp ::
forall b r m n.
(MonadBuildSchema b r m n, AggregationPredicatesSchema b) =>
TableInfo b ->
boolExpInternal ::
forall b r m n name.
( Typeable name,
Ord name,
ToTxt name,
MonadBuildSchema b r m n,
AggregationPredicatesSchema b
) =>
GQLNameIdentifier ->
[FieldInfo b] ->
G.Description ->
name ->
SchemaT r m (Maybe (InputFieldsParser n [AggregationPredicates b (UnpreparedValue b)])) ->
SchemaT r m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
boolExp tableInfo = do
boolExpInternal gqlName fieldInfos description memoizeKey mkAggPredParser = do
sourceInfo :: SourceInfo b <- asks getter
P.memoizeOn 'boolExp (_siName sourceInfo, tableName) do
tableGQLName <- getTableIdentifierName tableInfo
P.memoizeOn 'boolExpInternal (_siName sourceInfo, memoizeKey) do
let customization = _siCustomization sourceInfo
tCase = _rscNamingConvention customization
mkTypename = runMkTypename $ _rscTypeNames customization
name = mkTypename $ applyTypeNameCaseIdentifier tCase $ mkTableBoolExpTypeName tableGQLName
description =
G.Description $
"Boolean expression to filter rows from the table "
<> tableName
<<> ". All fields are combined with a logical 'AND'."
name = mkTypename $ applyTypeNameCaseIdentifier tCase $ mkTableBoolExpTypeName gqlName
fieldInfos <- tableSelectFields tableInfo
tableFieldParsers <- catMaybes <$> traverse mkField fieldInfos
-- TODO: This naming is somewhat unsatifactory..
aggregationPredicatesParser' <- fromMaybe (pure []) <$> aggregationPredicatesParser tableInfo
recur <- boolExp tableInfo
aggregationPredicatesParser' <- fromMaybe (pure []) <$> mkAggPredParser
recur <- boolExpInternal gqlName fieldInfos description memoizeKey mkAggPredParser
-- Bafflingly, ApplicativeDo doesnt work if we inline this definition (I
-- think the TH splices throw it off), so we have to define it separately.
let connectiveFieldParsers =
@ -114,8 +122,6 @@ boolExp tableInfo = do
aggregationPredicateFields <- map (BoolField . AVAggregationPredicates) <$> aggregationPredicatesParser'
pure (tableFields ++ specialFields ++ aggregationPredicateFields)
where
tableName = tableInfoName tableInfo
mkField ::
FieldInfo b ->
SchemaT r m (Maybe (InputFieldsParser n (Maybe (AnnBoolExpFld b (UnpreparedValue b)))))
@ -133,7 +139,7 @@ boolExp tableInfo = do
(fmap . fmap) partialSQLExpToUnpreparedValue $
maybe annBoolExpTrue spiFilter $
tableSelectPermissions roleName remoteTableInfo
remoteBoolExp <- lift $ boolExp remoteTableInfo
remoteBoolExp <- lift $ tableBoolExp remoteTableInfo
pure $ fmap (AVRelationship relationshipInfo . andAnnBoolExps remoteTableFilter) remoteBoolExp
FIComputedField ComputedFieldInfo {..} -> do
let ComputedFieldFunction {..} = _cfiFunction
@ -149,13 +155,83 @@ boolExp tableInfo = do
ReturnsScalar scalarType -> lift $ fmap CFBEScalar <$> comparisonExps @b (ColumnScalar scalarType)
ReturnsTable table -> do
info <- askTableInfo table
lift $ fmap (CFBETable table) <$> boolExp info
lift $ fmap (CFBETable table) <$> tableBoolExp info
ReturnsOthers -> hoistMaybe Nothing
_ -> hoistMaybe Nothing
-- Using remote relationship fields in boolean expressions is not supported.
FIRemoteRelationship _ -> empty
-- |
-- > input type_bool_exp {
-- > _or: [type_bool_exp!]
-- > _and: [type_bool_exp!]
-- > _not: type_bool_exp
-- > column: type_comparison_exp
-- > ...
-- > }
-- | Boolean expression for custom return types
customTypeBoolExp ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
customTypeBoolExp name customReturnType =
case toFieldInfo customReturnType of
Nothing -> throw500 $ "Error creating fields for custom type " <> tshow customReturnType
Just fieldInfo -> do
let gqlName = mkTableBoolExpTypeName (C.fromCustomName name)
-- Aggregation parsers let us say things like, "select all authors
-- with at least one article": they are predicates based on the
-- object's relationship with some other entity.
--
-- Currently, custom return types can't be defined to have
-- relationships to other entities, and so they don't support
-- aggregation predicates.
--
-- If you're here because you've been asked to implement them, this
-- is where you want to put the parser.
mkAggPredParser = pure (pure mempty)
memoizeKey = name
description =
G.Description $
"Boolean expression to filter rows from the custom return type for "
<> name
<<> ". All fields are combined with a logical 'AND'."
in boolExpInternal gqlName fieldInfo description memoizeKey mkAggPredParser
-- |
-- > input type_bool_exp {
-- > _or: [type_bool_exp!]
-- > _and: [type_bool_exp!]
-- > _not: type_bool_exp
-- > column: type_comparison_exp
-- > ...
-- > }
-- | Booleans expressions for tables
tableBoolExp ::
forall b r m n.
(MonadBuildSchema b r m n, AggregationPredicatesSchema b) =>
TableInfo b ->
SchemaT r m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
tableBoolExp tableInfo = do
gqlName <- getTableIdentifierName tableInfo
fieldInfos <- tableSelectFields tableInfo
let mkAggPredParser = aggregationPredicatesParser tableInfo
let description =
G.Description $
"Boolean expression to filter rows from the table "
<> tableInfoName tableInfo
<<> ". All fields are combined with a logical 'AND'."
let memoizeKey = tableInfoName tableInfo
boolExpInternal gqlName fieldInfos description memoizeKey mkAggPredParser
{- Note [Nullability in comparison operators]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -109,7 +109,7 @@ defaultAggregationPredicatesParser aggFns ti = runMaybeT do
aggPredDistinct <- fuse $ return $ fieldOptionalDefault Name._distinct Nothing False P.boolean
let aggPredFunctionName = fnName
aggPredPredicate <- fuse $ P.field Name._predicate Nothing <$> lift (comparisonExps @b (ColumnScalar fnReturnType))
aggPredFilter <- fuse $ P.fieldOptional Name._filter Nothing <$> lift (boolExp relTable)
aggPredFilter <- fuse $ P.fieldOptional Name._filter Nothing <$> lift (tableBoolExp relTable)
pure $ AggregationPredicate {..}
)
where

View File

@ -392,7 +392,7 @@ deleteFromTable scenario tableInfo fieldName description = runMaybeT $ do
lift do
let whereName = Name._where
whereDesc = "filter the rows which have to be deleted"
whereArg <- P.field whereName (Just whereDesc) <$> boolExp tableInfo
whereArg <- P.field whereName (Just whereDesc) <$> tableBoolExp tableInfo
selection <- mutationSelectionSet tableInfo
let columns = tableColumns tableInfo
pure $

View File

@ -2,7 +2,8 @@
{-# LANGUAGE TemplateHaskellQuotes #-}
module Hasura.GraphQL.Schema.OrderBy
( orderByExp,
( tableOrderByExp,
customTypeOrderByExp,
)
where
@ -10,6 +11,9 @@ import Data.Has
import Data.HashMap.Strict.Extended qualified as HashMap
import Data.Text.Casing qualified as C
import Data.Text.Extended
import Hasura.Base.Error
import Hasura.CustomReturnType (CustomReturnType)
import Hasura.CustomReturnType.Common (toFieldInfo)
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Common
@ -38,6 +42,7 @@ import Hasura.RQL.Types.Source
import Hasura.RQL.Types.SourceCustomization
import Hasura.RQL.Types.Table
import Language.GraphQL.Draft.Syntax qualified as G
import Type.Reflection
{-# INLINE orderByOperator #-}
orderByOperator ::
@ -60,23 +65,48 @@ orderByOperator tCase sourceInfo = case tCase of
-- > coln: order_by
-- > obj-rel: <remote-table>_order_by
-- > }
orderByExp ::
customTypeOrderByExp ::
forall b r m n.
MonadBuildSchema b r m n =>
TableInfo b ->
( MonadBuildSchema b r m n
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (Parser 'Input n [IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)])
orderByExp tableInfo = do
customTypeOrderByExp name customReturnType =
case toFieldInfo customReturnType of
Nothing -> throw500 $ "Error creating fields for custom type " <> tshow name
Just tableFields -> do
let description =
G.Description $
"Ordering options when selecting data from " <> name <<> "."
memoizeKey = name
orderByExpInternal (C.fromCustomName name) description tableFields memoizeKey
-- | Corresponds to an object type for an order by.
--
-- > input table_order_by {
-- > col1: order_by
-- > col2: order_by
-- > . .
-- > . .
-- > coln: order_by
-- > obj-rel: <remote-table>_order_by
-- > }
orderByExpInternal ::
forall b r m n name.
(Ord name, Typeable name, MonadBuildSchema b r m n) =>
C.GQLNameIdentifier ->
G.Description ->
[FieldInfo b] ->
name ->
SchemaT r m (Parser 'Input n [IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)])
orderByExpInternal gqlName description tableFields memoizeKey = do
sourceInfo <- asks getter
P.memoizeOn 'orderByExp (_siName sourceInfo, tableInfoName tableInfo) do
P.memoizeOn 'orderByExpInternal (_siName sourceInfo, memoizeKey) do
let customization = _siCustomization sourceInfo
tCase = _rscNamingConvention customization
mkTypename = runMkTypename $ _rscTypeNames customization
tableGQLName <- getTableIdentifierName tableInfo
let name = mkTypename $ applyTypeNameCaseIdentifier tCase $ mkTableOrderByTypeName tableGQLName
description =
G.Description $
"Ordering options when selecting data from " <> tableInfoName tableInfo <<> "."
tableFields <- tableSelectFields tableInfo
let name = mkTypename $ applyTypeNameCaseIdentifier tCase $ mkTableOrderByTypeName gqlName
fieldParsers <- sequenceA . catMaybes <$> traverse (mkField sourceInfo tCase) tableFields
pure $ concat . catMaybes <$> P.object name (Just description) fieldParsers
where
@ -103,7 +133,7 @@ orderByExp tableInfo = do
let newPerms = fmap partialSQLExpToUnpreparedValue <$> spiFilter perms
case riType relationshipInfo of
ObjRel -> do
otherTableParser <- lift $ orderByExp remoteTableInfo
otherTableParser <- lift $ tableOrderByExp remoteTableInfo
pure $ do
otherTableOrderBy <- join <$> P.fieldOptional fieldName Nothing (P.nullable otherTableParser)
pure $ fmap (map $ fmap $ IR.AOCObjectRelation relationshipInfo newPerms) otherTableOrderBy
@ -151,6 +181,30 @@ orderByExp tableInfo = do
ReturnsOthers -> empty
FIRemoteRelationship _ -> empty
-- | Corresponds to an object type for an order by.
--
-- > input table_order_by {
-- > col1: order_by
-- > col2: order_by
-- > . .
-- > . .
-- > coln: order_by
-- > obj-rel: <remote-table>_order_by
-- > }
tableOrderByExp ::
forall b r m n.
MonadBuildSchema b r m n =>
TableInfo b ->
SchemaT r m (Parser 'Input n [IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)])
tableOrderByExp tableInfo = do
tableGQLName <- getTableIdentifierName tableInfo
tableFields <- tableSelectFields tableInfo
let description =
G.Description $
"Ordering options when selecting data from " <> tableInfoName tableInfo <<> "."
memoizeKey = tableInfoName tableInfo
orderByExpInternal tableGQLName description tableFields memoizeKey
-- FIXME!
-- those parsers are directly using Postgres' SQL representation of
-- order, rather than using a general intermediary representation

View File

@ -13,6 +13,8 @@ module Hasura.GraphQL.Schema.Select
defaultSelectTableAggregate,
defaultTableArgs,
defaultTableSelectionSet,
defaultCustomTypeArgs,
defaultCustomTypeSelectionSet,
tableAggregationFields,
tableConnectionArgs,
tableConnectionSelectionSet,
@ -23,6 +25,7 @@ module Hasura.GraphQL.Schema.Select
tableOffsetArg,
tablePermissionsInfo,
tableSelectionList,
customTypeSelectionList,
)
where
@ -41,6 +44,7 @@ import Data.Text.Extended
import Hasura.Backends.Postgres.SQL.Types qualified as Postgres
import Hasura.Base.Error
import Hasura.Base.ErrorMessage (toErrorMessage)
import Hasura.CustomReturnType (CustomReturnType (..))
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Parser.Internal.Parser qualified as P
import Hasura.GraphQL.Schema.Backend
@ -463,6 +467,65 @@ tableSelectionList ::
tableSelectionList tableInfo =
fmap nonNullableObjectList <$> tableSelectionSet tableInfo
defaultCustomTypeSelectionSet ::
forall b r m n.
( MonadBuildSchema b r m n
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (Maybe (Parser 'Output n (AnnotatedFields b)))
defaultCustomTypeSelectionSet name customReturnType = runMaybeT $ do
let parseField (column, scalarType) = do
let -- At least in its first draft, we assume that all fields in a
-- custom return type are non-nullable. See NDAT-516 for progress.
nullability = False
-- Currently, row-level permissions are unsupported for custom
-- return types. In fact, permissions are unsupported: the feature
-- is assumed to be admin-only. If you've been asked to implement
-- permissions, this is the place.
caseBoolExpUnpreparedValue = Nothing
columnType = ColumnScalar scalarType
pathArg = scalarSelectionArgumentsParser columnType
-- Currently, we don't support descriptions for individual columns
-- of a custom return type. In future, we might want to add this as
-- an optional field in 'CustomReturnTypeRow'.
description = Nothing
columnName <- hoistMaybe (G.mkName (toTxt column))
field <- lift $ columnParser columnType (G.Nullability nullability)
pure $!
P.selection columnName description pathArg field
<&> IR.mkAnnColumnField column columnType caseBoolExpUnpreparedValue
let fieldName = name
parsers <- traverse parseField (Map.toList (crtColumns customReturnType))
let -- Currently, there's no useful description for a custom return
-- type. In future, custom return types will be declared with an
-- optional `description` key, which will be passed through to
-- here. See NDAT-518 for progress.
description = Nothing
-- We entirely ignore Relay for now.
implementsInterfaces = mempty
pure $
P.selectionSetObject fieldName description parsers implementsInterfaces
<&> parsedSelectionsToFields IR.AFExpression
customTypeSelectionList ::
(MonadBuildSchema b r m n, BackendCustomTypeSelectSchema b) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (Maybe (Parser 'Output n (AnnotatedFields b)))
customTypeSelectionList name nativeQuery =
fmap nonNullableObjectList <$> customTypeSelectionSet name nativeQuery
-- | Converts an output type parser from object_type to [object_type!]!
nonNullableObjectList :: Parser 'Output m a -> Parser 'Output m a
nonNullableObjectList =
@ -604,41 +667,96 @@ defaultTableArgs tableInfo = do
whereParser <- tableWhereArg tableInfo
orderByParser <- tableOrderByArg tableInfo
distinctParser <- tableDistinctArg tableInfo
let result = do
whereArg <- whereParser
orderByArg <- orderByParser
limitArg <- tableLimitArg
offsetArg <- tableOffsetArg
distinctArg <- distinctParser
pure $
IR.SelectArgs
{ IR._saWhere = whereArg,
IR._saOrderBy = orderByArg,
IR._saLimit = limitArg,
IR._saOffset = offsetArg,
IR._saDistinct = distinctArg
}
defaultArgsParser whereParser orderByParser distinctParser
-- | Argument to filter rows returned from table selection
-- > where: table_bool_exp
customTypeWhereArg ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (InputFieldsParser n (Maybe (IR.AnnBoolExp b (IR.UnpreparedValue b))))
customTypeWhereArg name customReturnType = do
boolExpParser <- customTypeBoolExp name customReturnType
pure $
result `P.bindFields` \args -> do
sequence_ do
orderBy <- IR._saOrderBy args
distinct <- IR._saDistinct args
Just $ validateArgs orderBy distinct
pure args
fmap join $
P.fieldOptional whereName whereDesc $
P.nullable boolExpParser
where
validateArgs orderByCols distinctCols = do
let colsLen = length distinctCols
initOrderBys = take colsLen $ NE.toList orderByCols
initOrdByCols = flip mapMaybe initOrderBys $ \ob ->
case IR.obiColumn ob of
IR.AOCColumn columnInfo -> Just $ ciColumn columnInfo
_ -> Nothing
isValid =
(colsLen == length initOrdByCols)
&& all (`elem` initOrdByCols) (toList distinctCols)
unless isValid $
parseError
"\"distinct_on\" columns must match initial \"order_by\" columns"
whereName = Name._where
whereDesc = Just $ G.Description "filter the rows returned"
-- | Argument to sort rows returned from table selection
-- > order_by: [table_order_by!]
customTypeOrderByArg ::
forall b r m n.
( MonadBuildSchema b r m n
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (InputFieldsParser n (Maybe (NonEmpty (IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)))))
customTypeOrderByArg name customReturnType = do
tCase <- retrieve $ _rscNamingConvention . _siCustomization @b
orderByParser <- customTypeOrderByExp name customReturnType
let orderByName = applyFieldNameCaseCust tCase Name._order_by
orderByDesc = Just $ G.Description "sort the rows by one or more columns"
pure $ do
maybeOrderByExps <-
fmap join $
P.fieldOptional orderByName orderByDesc $
P.nullable $
P.list orderByParser
pure $ maybeOrderByExps >>= NE.nonEmpty . concat
-- | Argument to distinct select on columns returned from table selection
-- > distinct_on: [table_select_column!]
customTypeDistinctArg ::
forall b r m n.
( MonadBuildSchema b r m n
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (InputFieldsParser n (Maybe (NonEmpty (Column b))))
customTypeDistinctArg name customReturnType = do
tCase <- retrieve $ _rscNamingConvention . _siCustomization @b
let maybeColumnDefinitions =
traverse definitionFromTypeRow (Map.toList (crtColumns customReturnType))
>>= NE.nonEmpty
case (,) <$> G.mkName "_enum_name" <*> maybeColumnDefinitions of
Nothing -> throw500 $ "Error creating an enum name for custom type " <> tshow customReturnType
Just (enum', columnDefinitions) -> do
let enumName = name <> enum'
description = Nothing
columnsEnum = Just $ P.enum @n enumName description columnDefinitions
distinctOnName = applyFieldNameCaseCust tCase Name._distinct_on
distinctOnDesc = Just $ G.Description "distinct select on columns"
pure do
maybeDistinctOnColumns <-
join . join
<$> for
columnsEnum
(P.fieldOptional distinctOnName distinctOnDesc . P.nullable . P.list)
pure $ maybeDistinctOnColumns >>= NE.nonEmpty
where
definitionFromTypeRow :: (Column b, ScalarType b) -> Maybe (P.Definition P.EnumValueInfo, Column b)
definitionFromTypeRow (name', _) = do
columnName <- G.mkName (toTxt name')
let definition =
P.Definition
{ dName = columnName,
dDescription = Nothing,
dOrigin = Nothing,
dDirectives = mempty,
dInfo = P.EnumValueInfo
}
pure (definition, name')
-- | Argument to filter rows returned from table selection
-- > where: table_bool_exp
@ -650,7 +768,7 @@ tableWhereArg ::
TableInfo b ->
SchemaT r m (InputFieldsParser n (Maybe (IR.AnnBoolExp b (IR.UnpreparedValue b))))
tableWhereArg tableInfo = do
boolExpParser <- boolExp tableInfo
boolExpParser <- tableBoolExp tableInfo
pure $
fmap join $
P.fieldOptional whereName whereDesc $
@ -668,7 +786,7 @@ tableOrderByArg ::
SchemaT r m (InputFieldsParser n (Maybe (NonEmpty (IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)))))
tableOrderByArg tableInfo = do
tCase <- retrieve $ _rscNamingConvention . _siCustomization @b
orderByParser <- orderByExp tableInfo
orderByParser <- tableOrderByExp tableInfo
let orderByName = applyFieldNameCaseCust tCase Name._order_by
orderByDesc = Just $ G.Description "sort the rows by one or more columns"
pure $ do
@ -1024,6 +1142,67 @@ tableAggregationFields tableInfo = do
in P.subselection_ fieldName Nothing subselectionParser
<&> IR.AFOp . IR.AggregateOp opText
-- | shared implementation between tables and custom types
defaultArgsParser ::
forall b r m n.
( MonadBuildSchema b r m n
) =>
InputFieldsParser n (Maybe (AnnBoolExp b (IR.UnpreparedValue b))) ->
InputFieldsParser n (Maybe (NonEmpty (IR.AnnotatedOrderByItemG b (IR.UnpreparedValue b)))) ->
InputFieldsParser n (Maybe (NonEmpty (Column b))) ->
SchemaT r m (InputFieldsParser n (SelectArgs b))
defaultArgsParser whereParser orderByParser distinctParser = do
let result = do
whereArg <- whereParser
orderByArg <- orderByParser
limitArg <- tableLimitArg
offsetArg <- tableOffsetArg
distinctArg <- distinctParser
pure $
IR.SelectArgs
{ IR._saWhere = whereArg,
IR._saOrderBy = orderByArg,
IR._saLimit = limitArg,
IR._saOffset = offsetArg,
IR._saDistinct = distinctArg
}
pure $
result `P.bindFields` \args -> do
sequence_ do
orderBy <- IR._saOrderBy args
distinct <- IR._saDistinct args
Just $ validateArgs orderBy distinct
pure args
where
validateArgs orderByCols distinctCols = do
let colsLen = length distinctCols
initOrderBys = take colsLen $ NE.toList orderByCols
initOrdByCols = flip mapMaybe initOrderBys $ \ob ->
case IR.obiColumn ob of
IR.AOCColumn columnInfo -> Just $ ciColumn columnInfo
_ -> Nothing
isValid =
(colsLen == length initOrdByCols)
&& all (`elem` initOrdByCols) (toList distinctCols)
unless isValid $
parseError
"\"distinct_on\" columns must match initial \"order_by\" columns"
defaultCustomTypeArgs ::
forall b r m n.
( MonadBuildSchema b r m n,
AggregationPredicatesSchema b
) =>
G.Name ->
CustomReturnType b ->
SchemaT r m (InputFieldsParser n (SelectArgs b))
defaultCustomTypeArgs name customReturnType = do
whereParser <- customTypeWhereArg name customReturnType
orderByParser <- customTypeOrderByArg name customReturnType
distinctParser <- customTypeDistinctArg name customReturnType
defaultArgsParser whereParser orderByParser distinctParser
-- | An individual field of a table
--
-- > field_name(arg_name: arg_type, ...): field_type

View File

@ -11,7 +11,7 @@ import Data.Has (Has (getter))
import Data.Text.Casing (GQLNameIdentifier)
import Data.Text.Extended (toTxt, (<>>))
import Hasura.GraphQL.Schema.Backend (BackendTableSelectSchema (..), BackendUpdateOperatorsSchema (..), MonadBuildSchema)
import Hasura.GraphQL.Schema.BoolExp (AggregationPredicatesSchema, boolExp)
import Hasura.GraphQL.Schema.BoolExp (AggregationPredicatesSchema, tableBoolExp)
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Mutation (mutationSelectionSet, primaryKeysArguments)
import Hasura.GraphQL.Schema.NamingCase
@ -97,7 +97,7 @@ updateTable mkSingleBatchUpdateVariant scenario tableInfo tableGqlName = runMayb
let parseOutput = lift $ fmap MOutMultirowFields <$> mutationSelectionSet tableInfo
buildAnnotatedUpdateGField scenario tableInfo updateTableFieldName updateTableFieldDescription parseOutput $ \updatePerms -> lift $ do
whereArg <- P.field Name._where (Just whereDesc) <$> boolExp tableInfo
whereArg <- P.field Name._where (Just whereDesc) <$> tableBoolExp tableInfo
updateOperators <- parseUpdateOperators tableInfo updatePerms
pure $ mkSingleBatchUpdateVariant <$> (UpdateBatch <$> updateOperators <*> whereArg)
where
@ -138,7 +138,7 @@ updateTableMany mkSingleBatchUpdateVariant scenario tableInfo tableGqlName = run
. P.list
. P.object updatesObjectName Nothing
<$> do
whereExp <- P.field Name._where (Just whereDesc) <$> boolExp tableInfo
whereExp <- P.field Name._where (Just whereDesc) <$> tableBoolExp tableInfo
pure $ UpdateBatch <$> updateOperators <*> whereExp
where
tableName = tableInfoName tableInfo

View File

@ -26,12 +26,13 @@ import Control.Lens (preview, (^?))
import Data.Aeson
import Data.Environment qualified as Env
import Hasura.Base.Error
import Hasura.CustomReturnType (CustomReturnType)
import Hasura.EncJSON
import Hasura.Metadata.DTO.Utils (codecNamePrefix)
import Hasura.NativeQuery.Metadata (NativeQueryArgumentName, NativeQueryInfo (..), parseInterpolatedQuery)
import Hasura.NativeQuery.Types
import Hasura.Prelude
import Hasura.RQL.Types.Backend (Backend, ScalarType, SourceConnConfiguration, TableName)
import Hasura.RQL.Types.Backend (Backend, ScalarType, SourceConnConfiguration)
import Hasura.RQL.Types.Common (SourceName, sourceNameToText, successMsg)
import Hasura.RQL.Types.Metadata
import Hasura.RQL.Types.Metadata.Backend
@ -50,7 +51,7 @@ data TrackNativeQuery (b :: BackendType) = TrackNativeQuery
tnqCode :: Text,
tnqArguments :: HashMap NativeQueryArgumentName (ScalarType b),
tnqDescription :: Maybe Text,
tnqReturns :: TableName b
tnqReturns :: CustomReturnType b
}
instance (Backend b) => HasCodec (TrackNativeQuery b) where

View File

@ -19,6 +19,7 @@ import Autodocodec qualified as AC
import Data.Aeson
import Data.Bifunctor (first)
import Data.Text qualified as T
import Hasura.CustomReturnType (CustomReturnType)
import Hasura.Metadata.DTO.Utils (codecNamePrefix)
import Hasura.NativeQuery.Types
import Hasura.Prelude hiding (first)
@ -106,7 +107,7 @@ instance NFData (NativeQueryArgumentName)
data NativeQueryInfo (b :: BackendType) = NativeQueryInfo
{ nqiRootFieldName :: NativeQueryName,
nqiCode :: InterpolatedQuery NativeQueryArgumentName,
nqiReturns :: TableName b,
nqiReturns :: CustomReturnType b,
nqiArguments :: HashMap NativeQueryArgumentName (ScalarType b),
nqiDescription :: Maybe Text
}

View File

@ -6,26 +6,23 @@ import Data.Has (Has (getter))
import Data.HashMap.Strict qualified as HM
import Data.Monoid (Ap (Ap, getAp))
import Hasura.GraphQL.Schema.Backend
( BackendSchema (columnParser),
BackendTableSelectSchema (tableArguments),
( BackendCustomTypeSelectSchema (..),
BackendSchema (columnParser),
MonadBuildSchema,
)
import Hasura.GraphQL.Schema.Common
( SchemaContext (scRole),
SchemaT,
askTableInfo,
( SchemaT,
retrieve,
)
import Hasura.GraphQL.Schema.Options qualified as Options
import Hasura.GraphQL.Schema.Parser qualified as P
import Hasura.GraphQL.Schema.Select
( tablePermissionsInfo,
tableSelectionList,
( customTypeSelectionList,
)
import Hasura.GraphQL.Schema.Table (tableSelectPermissions)
import Hasura.NativeQuery.IR (NativeQuery (..))
import Hasura.NativeQuery.Metadata
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp (gBoolExpTrue)
import Hasura.RQL.IR.Root (RemoteRelationshipField)
import Hasura.RQL.IR.Select (QueryDB (QDBMultipleRows))
import Hasura.RQL.IR.Select qualified as IR
@ -41,7 +38,6 @@ import Hasura.RQL.Types.Source
import Hasura.RQL.Types.SourceCustomization
( ResolvedSourceCustomization (_rscNamingConvention),
)
import Hasura.RQL.Types.Table (tableInfoName)
import Hasura.SQL.AnyBackend (mkAnyBackend)
import Language.GraphQL.Draft.Syntax qualified as G
import Language.GraphQL.Draft.Syntax.QQ qualified as G
@ -49,7 +45,7 @@ import Language.GraphQL.Draft.Syntax.QQ qualified as G
defaultBuildNativeQueryRootFields ::
forall b r m n.
( MonadBuildSchema b r m n,
BackendTableSelectSchema b
BackendCustomTypeSelectSchema b
) =>
NativeQueryInfo b ->
SchemaT
@ -57,20 +53,19 @@ defaultBuildNativeQueryRootFields ::
m
(Maybe (P.FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))))
defaultBuildNativeQueryRootFields NativeQueryInfo {..} = runMaybeT $ do
tableInfo <- askTableInfo @b nqiReturns
let fieldName = getNativeQueryName nqiRootFieldName
nativeQueryArgsParser <- nativeQueryArgumentsSchema @b @r @m @n fieldName nqiArguments
sourceInfo :: SourceInfo b <- asks getter
let sourceName = _siName sourceInfo
tableName = tableInfoName tableInfo
tCase = _rscNamingConvention $ _siCustomization sourceInfo
description = G.Description <$> nqiDescription
stringifyNumbers <- retrieve Options.soStringifyNumbers
roleName <- retrieve scRole
selectionSetParser <- MaybeT $ tableSelectionList @b @r @m @n tableInfo
tableArgsParser <- lift $ tableArguments @b @r @m @n tableInfo
selectPermissions <- hoistMaybe $ tableSelectPermissions roleName tableInfo
stringifyNumbers <- retrieve Options.soStringifyNumbers
selectionSetParser <- MaybeT $ customTypeSelectionList @b @r @m @n (getNativeQueryName nqiRootFieldName) nqiReturns
customTypesArgsParser <- lift $ customTypeArguments @b @r @m @n (getNativeQueryName nqiRootFieldName) nqiReturns
let interpolatedQuery nqArgs =
InterpolatedQuery $
@ -86,8 +81,15 @@ defaultBuildNativeQueryRootFields NativeQueryInfo {..} = runMaybeT $ do
(getInterpolatedQuery nqiCode)
pure $
P.setFieldParserOrigin (MO.MOSourceObjId sourceName (mkAnyBackend $ MO.SMOTable @b tableName)) $
P.subselection fieldName description ((,) <$> tableArgsParser <*> nativeQueryArgsParser) selectionSetParser
P.setFieldParserOrigin (MO.MOSourceObjId sourceName (mkAnyBackend $ MO.SMONativeQuery @b nqiRootFieldName)) $
P.subselection
fieldName
description
( (,)
<$> customTypesArgsParser
<*> nativeQueryArgsParser
)
selectionSetParser
<&> \((args, nqArgs), fields) ->
QDBMultipleRows $
IR.AnnSelectG
@ -99,7 +101,7 @@ defaultBuildNativeQueryRootFields NativeQueryInfo {..} = runMaybeT $ do
nqArgs,
nqInterpolatedQuery = interpolatedQuery nqArgs
},
IR._asnPerm = tablePermissionsInfo selectPermissions,
IR._asnPerm = IR.TablePerm gBoolExpTrue Nothing,
IR._asnArgs = args,
IR._asnStrfyNum = stringifyNumbers,
IR._asnNamingConvention = Just tCase