mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
Co-authored-by: Antoine Leblanc <antoine@hasura.io> GITHUB_PR_NUMBER: 6417 GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/6417 GitOrigin-RevId: 37b67a4d04e0ed3b16fc5fc9bf025b24b1f1bf6e
This commit is contained in:
parent
647504ef99
commit
513a3d0c19
@ -88,6 +88,8 @@ and be accessible according to the permissions that were configured for the role
|
|||||||
- server: various changes to ensure timely cleanup of background threads and other resources in the event of a SIGTERM signal.
|
- server: various changes to ensure timely cleanup of background threads and other resources in the event of a SIGTERM signal.
|
||||||
- server: fix issue when the `relationships` field in `objects` field is passed `[]` in the `set_custom_types` API (fix #6357)
|
- server: fix issue when the `relationships` field in `objects` field is passed `[]` in the `set_custom_types` API (fix #6357)
|
||||||
- server: fix issue with event triggers defined on a table which is partitioned (fixes #6261)
|
- server: fix issue with event triggers defined on a table which is partitioned (fixes #6261)
|
||||||
|
- server: action array relationships now support the same input arguments (such as where or distinct_on) as usual relationships
|
||||||
|
- server: action array relationships now support aggregate relationships
|
||||||
- server: fix issue with non-optional fields of the remote schema being added as optional in the graphql-engine (fix #6401)
|
- server: fix issue with non-optional fields of the remote schema being added as optional in the graphql-engine (fix #6401)
|
||||||
- server: accept new config `allowed_skew` in JWT config to provide leeway for JWT expiry (fixes #2109)
|
- server: accept new config `allowed_skew` in JWT config to provide leeway for JWT expiry (fixes #2109)
|
||||||
- server: fix issue with query actions with relationship with permissions configured on the remote table (fix #6385)
|
- server: fix issue with query actions with relationship with permissions configured on the remote table (fix #6385)
|
||||||
|
@ -4,20 +4,21 @@ module Hasura.GraphQL.Schema.Action
|
|||||||
, actionAsyncQuery
|
, actionAsyncQuery
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Has
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
import qualified Language.GraphQL.Draft.Syntax as G
|
import qualified Language.GraphQL.Draft.Syntax as G
|
||||||
|
|
||||||
|
import Data.Has
|
||||||
|
import Data.Text.Extended
|
||||||
|
import Data.Text.NonEmpty
|
||||||
|
|
||||||
import qualified Hasura.GraphQL.Parser as P
|
import qualified Hasura.GraphQL.Parser as P
|
||||||
import qualified Hasura.GraphQL.Parser.Internal.Parser as P
|
import qualified Hasura.GraphQL.Parser.Internal.Parser as P
|
||||||
import qualified Hasura.RQL.DML.Internal as RQL
|
import qualified Hasura.RQL.DML.Internal as RQL
|
||||||
import qualified Hasura.RQL.IR.Select as RQL
|
import qualified Hasura.RQL.IR.Select as RQL
|
||||||
|
|
||||||
import Data.Text.Extended
|
|
||||||
import Data.Text.NonEmpty
|
|
||||||
import Hasura.Backends.Postgres.SQL.Types
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
import Hasura.Backends.Postgres.SQL.Value
|
import Hasura.Backends.Postgres.SQL.Value
|
||||||
import Hasura.GraphQL.Parser (FieldParser, InputFieldsParser, Kind (..),
|
import Hasura.GraphQL.Parser (FieldParser, InputFieldsParser, Kind (..),
|
||||||
@ -171,7 +172,7 @@ actionOutputFields annotatedObject = do
|
|||||||
scalarOrEnumFields = map scalarOrEnumFieldParser $ toList $ _otdFields outputObject
|
scalarOrEnumFields = map scalarOrEnumFieldParser $ toList $ _otdFields outputObject
|
||||||
relationshipFields <- forM (_otdRelationships outputObject) $ traverse relationshipFieldParser
|
relationshipFields <- forM (_otdRelationships outputObject) $ traverse relationshipFieldParser
|
||||||
let allFieldParsers = scalarOrEnumFields <>
|
let allFieldParsers = scalarOrEnumFields <>
|
||||||
maybe [] (catMaybes . toList) relationshipFields
|
maybe [] (concat . catMaybes . toList) relationshipFields
|
||||||
outputTypeName = unObjectTypeName $ _otdName outputObject
|
outputTypeName = unObjectTypeName $ _otdName outputObject
|
||||||
outputTypeDescription = _otdDescription outputObject
|
outputTypeDescription = _otdDescription outputObject
|
||||||
pure $ P.selectionSet outputTypeName outputTypeDescription allFieldParsers
|
pure $ P.selectionSet outputTypeName outputTypeDescription allFieldParsers
|
||||||
@ -195,26 +196,37 @@ actionOutputFields annotatedObject = do
|
|||||||
|
|
||||||
relationshipFieldParser
|
relationshipFieldParser
|
||||||
:: TypeRelationship (TableInfo 'Postgres) (ColumnInfo 'Postgres)
|
:: TypeRelationship (TableInfo 'Postgres) (ColumnInfo 'Postgres)
|
||||||
-> m (Maybe (FieldParser n (RQL.AnnFieldG 'Postgres (UnpreparedValue 'Postgres))))
|
-> m (Maybe [FieldParser n (RQL.AnnFieldG 'Postgres (UnpreparedValue 'Postgres))])
|
||||||
relationshipFieldParser typeRelationship = runMaybeT do
|
relationshipFieldParser (TypeRelationship relName relType _ tableInfo fieldMapping) = runMaybeT do
|
||||||
let TypeRelationship relName relType _ tableInfo fieldMapping = typeRelationship
|
let tableName = _tciName $ _tiCoreInfo tableInfo
|
||||||
tableName = _tciName $ _tiCoreInfo tableInfo
|
|
||||||
fieldName = unRelationshipName relName
|
fieldName = unRelationshipName relName
|
||||||
|
tableRelName = RelName $ mkNonEmptyTextUnsafe $ G.unName fieldName
|
||||||
|
columnMapping = Map.fromList $ do
|
||||||
|
(k, v) <- Map.toList fieldMapping
|
||||||
|
pure (unsafePGCol $ G.unName $ unObjectFieldName k, pgiColumn v)
|
||||||
roleName <- lift askRoleName
|
roleName <- lift askRoleName
|
||||||
tablePerms <- MaybeT $ pure $ RQL.getPermInfoMaybe roleName PASelect tableInfo
|
tablePerms <- MaybeT $ pure $ RQL.getPermInfoMaybe roleName PASelect tableInfo
|
||||||
tableParser <- lift $ selectTable tableName fieldName Nothing tablePerms
|
case relType of
|
||||||
pure $ tableParser <&> \selectExp ->
|
ObjRel -> do
|
||||||
let tableRelName = RelName $ mkNonEmptyTextUnsafe $ G.unName fieldName
|
let desc = Just $ G.Description "An object relationship"
|
||||||
columnMapping = Map.fromList $
|
selectionSetParser <- lift $ tableSelectionSet tableName tablePerms
|
||||||
[ (unsafePGCol $ G.unName $ unObjectFieldName k, pgiColumn v)
|
pure $ pure $ P.nonNullableField $
|
||||||
| (k, v) <- Map.toList fieldMapping
|
P.subselection_ fieldName desc selectionSetParser
|
||||||
|
<&> \fields -> RQL.AFObjectRelation $ RQL.AnnRelationSelectG tableRelName columnMapping $
|
||||||
|
RQL.AnnObjectSelectG fields tableName $
|
||||||
|
fmapAnnBoolExp partialSQLExpToUnpreparedValue $ spiFilter tablePerms
|
||||||
|
ArrRel -> do
|
||||||
|
let desc = Just $ G.Description "An array relationship"
|
||||||
|
otherTableParser <- lift $ selectTable tableName fieldName desc tablePerms
|
||||||
|
let arrayRelField = otherTableParser <&> \selectExp -> RQL.AFArrayRelation $
|
||||||
|
RQL.ASSimple $ RQL.AnnRelationSelectG tableRelName columnMapping selectExp
|
||||||
|
relAggFieldName = fieldName <> $$(G.litName "_aggregate")
|
||||||
|
relAggDesc = Just $ G.Description "An aggregate relationship"
|
||||||
|
tableAggField <- lift $ selectTableAggregate tableName relAggFieldName relAggDesc tablePerms
|
||||||
|
pure $ catMaybes [ Just arrayRelField
|
||||||
|
, fmap (RQL.AFArrayRelation . RQL.ASAggregate . RQL.AnnRelationSelectG tableRelName columnMapping) <$> tableAggField
|
||||||
]
|
]
|
||||||
in case relType of
|
|
||||||
ObjRel -> RQL.AFObjectRelation $ RQL.AnnRelationSelectG tableRelName columnMapping $
|
|
||||||
RQL.AnnObjectSelectG (RQL._asnFields selectExp) tableName $
|
|
||||||
RQL._tpFilter $ RQL._asnPerm selectExp
|
|
||||||
ArrRel -> RQL.AFArrayRelation $ RQL.ASSimple $
|
|
||||||
RQL.AnnRelationSelectG tableRelName columnMapping selectExp
|
|
||||||
|
|
||||||
mkDefinitionList :: AnnotatedObjectType 'Postgres -> [(PGCol, ScalarType 'Postgres)]
|
mkDefinitionList :: AnnotatedObjectType 'Postgres -> [(PGCol, ScalarType 'Postgres)]
|
||||||
mkDefinitionList AnnotatedObjectType{..} =
|
mkDefinitionList AnnotatedObjectType{..} =
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
- description: Run create_user sync action mutation with valid email
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
mutation {
|
||||||
|
create_user(email: "clarke@gmail.com", name: "Clarke"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
user {
|
||||||
|
__typename
|
||||||
|
name
|
||||||
|
email
|
||||||
|
is_admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
create_user:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
user:
|
||||||
|
__typename: user
|
||||||
|
name: Clarke
|
||||||
|
email: clarke@gmail.com
|
||||||
|
is_admin: false
|
||||||
|
|
||||||
|
- description: Use user_by_email to get our user and test array relationship
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
get_user_by_email(email: "clarke@gmail.com"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
articles {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
get_user_by_email:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
articles:
|
||||||
|
- name: foo
|
||||||
|
- name: bar
|
||||||
|
- name: bar
|
||||||
|
|
||||||
|
- description: Use user_by_email to get our user and test array relationship with limit input parameter
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
get_user_by_email(email: "clarke@gmail.com"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
articles(limit: 1) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
get_user_by_email:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
articles:
|
||||||
|
- name: foo
|
||||||
|
|
||||||
|
|
||||||
|
- description: Use user_by_email to get our user and test array relationship with distinct input parameter
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
get_user_by_email(email: "clarke@gmail.com"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
articles(distinct_on: name) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
get_user_by_email:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
articles:
|
||||||
|
- name: bar
|
||||||
|
- name: foo
|
||||||
|
|
||||||
|
|
||||||
|
- description: Use user_by_email to get our user and test aggregate array relationship
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
get_user_by_email(email: "clarke@gmail.com"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
articles_aggregate {
|
||||||
|
aggregate {
|
||||||
|
max {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
get_user_by_email:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
articles:
|
||||||
|
- name: bar
|
||||||
|
- name: foo
|
@ -0,0 +1,48 @@
|
|||||||
|
- description: Run create_user sync action mutation with valid email
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
mutation {
|
||||||
|
create_user(email: "clarke@gmail.com", name: "Clarke"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
user {
|
||||||
|
__typename
|
||||||
|
name
|
||||||
|
email
|
||||||
|
is_admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
create_user:
|
||||||
|
__typename: UserId
|
||||||
|
id: 1
|
||||||
|
user:
|
||||||
|
__typename: user
|
||||||
|
name: Clarke
|
||||||
|
email: clarke@gmail.com
|
||||||
|
is_admin: false
|
||||||
|
|
||||||
|
- description: Use user_by_email to get our user and test object relationship
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
get_user_by_email(email: "clarke@gmail.com"){
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
user(limit: 4) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response:
|
||||||
|
errors:
|
||||||
|
- extensions:
|
||||||
|
path: $.selectionSet.get_user_by_email.selectionSet.user
|
||||||
|
code: validation-failed
|
||||||
|
message: '"user" has no argument named "limit"'
|
@ -10,12 +10,27 @@ args:
|
|||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
is_admin BOOLEAN NOT NULL DEFAULT false
|
is_admin BOOLEAN NOT NULL DEFAULT false
|
||||||
);
|
);
|
||||||
|
CREATE TABLE "article"(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
user_id INTEGER
|
||||||
|
);
|
||||||
|
INSERT INTO "article" (name, user_id) VALUES
|
||||||
|
('foo', 1),
|
||||||
|
('bar', 1),
|
||||||
|
('bar', 1),
|
||||||
|
('baz', 2);
|
||||||
|
|
||||||
- type: track_table
|
- type: track_table
|
||||||
args:
|
args:
|
||||||
name: user
|
name: user
|
||||||
schema: public
|
schema: public
|
||||||
|
|
||||||
|
- type: track_table
|
||||||
|
args:
|
||||||
|
name: article
|
||||||
|
schema: public
|
||||||
|
|
||||||
- type: set_custom_types
|
- type: set_custom_types
|
||||||
args:
|
args:
|
||||||
input_objects:
|
input_objects:
|
||||||
@ -46,6 +61,11 @@ args:
|
|||||||
remote_table: user
|
remote_table: user
|
||||||
field_mapping:
|
field_mapping:
|
||||||
id: id
|
id: id
|
||||||
|
- name: articles
|
||||||
|
type: array
|
||||||
|
remote_table: article
|
||||||
|
field_mapping:
|
||||||
|
id: user_id
|
||||||
|
|
||||||
- name: OutObject
|
- name: OutObject
|
||||||
fields:
|
fields:
|
||||||
|
@ -29,3 +29,4 @@ args:
|
|||||||
cascade: true
|
cascade: true
|
||||||
sql: |
|
sql: |
|
||||||
DROP TABLE "user";
|
DROP TABLE "user";
|
||||||
|
DROP TABLE "article";
|
||||||
|
@ -46,6 +46,12 @@ class TestActionsSyncWebsocket:
|
|||||||
def test_create_user_success(self, hge_ctx, transport):
|
def test_create_user_success(self, hge_ctx, transport):
|
||||||
check_query_f(hge_ctx, self.dir() + '/create_user_success.yaml', transport)
|
check_query_f(hge_ctx, self.dir() + '/create_user_success.yaml', transport)
|
||||||
|
|
||||||
|
def test_create_user_relationship(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/create_user_relationship.yaml', transport)
|
||||||
|
|
||||||
|
def test_create_user_relationship(self, hge_ctx, transport):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/create_user_relationship_fail.yaml', transport)
|
||||||
|
|
||||||
def test_create_users_fail(self, hge_ctx, transport):
|
def test_create_users_fail(self, hge_ctx, transport):
|
||||||
check_query_f(hge_ctx, self.dir() + '/create_users_fail.yaml', transport)
|
check_query_f(hge_ctx, self.dir() + '/create_users_fail.yaml', transport)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user