mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 06:18:04 +03:00
[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:
parent
96856f5de3
commit
3b42e704dd
@ -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
|
||||
|
@ -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|
|
||||
|
@ -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|
|
||||
|
@ -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 <-
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
37
server/src-lib/Hasura/CustomReturnType.hs
Normal file
37
server/src-lib/Hasura/CustomReturnType.hs
Normal 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)
|
36
server/src-lib/Hasura/CustomReturnType/Common.hs
Normal file
36
server/src-lib/Hasura/CustomReturnType/Common.hs
Normal 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}
|
||||
}
|
@ -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)
|
||||
|
@ -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 doesn’t 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]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 $
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user