这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@
- option to reload remote schemas in 'reload_metadata' API (fix #3792, #4117)
- console: disable selecting roles without permissions for bulk actions (close #4178) (#4195)
- console: show remote schema / event trigger intro sections always (#4044)
- server: fix postgres query error when computed fields included in mutation response (fix #4035)
2 changes: 1 addition & 1 deletion server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ insertMultipleObjects strfyNum role tn multiObjIns addCols mutOutput errP =
let affRows = sum $ map fst insResps
columnValues = catMaybes $ map snd insResps
cteExp <- mkSelCTEFromColVals tn tableColInfos columnValues
let sql = toSQL $ RR.mkMutationOutputExp tn (Just affRows) cteExp mutOutput strfyNum
let sql = toSQL $ RR.mkMutationOutputExp tn tableColInfos (Just affRows) cteExp mutOutput strfyNum
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sql) [] False

Expand Down
9 changes: 4 additions & 5 deletions server/src-lib/Hasura/RQL/DML/Mutation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ runMutation mut =
hasNestedFld $ _mOutput mut

mutateAndReturn :: Mutation -> Q.TxE QErr EncJSON
mutateAndReturn (Mutation qt (cte, p) mutationOutput _ strfyNum) =
mutateAndReturn (Mutation qt (cte, p) mutationOutput allCols strfyNum) =
encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith)
(toList p) True
where
selWith = mkMutationOutputExp qt Nothing cte mutationOutput strfyNum
selWith = mkMutationOutputExp qt allCols Nothing cte mutationOutput strfyNum

mutateAndSel :: Mutation -> Q.TxE QErr EncJSON
mutateAndSel (Mutation qt q mutationOutput allCols strfyNum) = do
-- Perform mutation and fetch unique columns
MutateResp _ columnVals <- mutateAndFetchCols qt allCols q strfyNum
selCTE <- mkSelCTEFromColVals qt allCols columnVals
let selWith = mkMutationOutputExp qt Nothing selCTE mutationOutput strfyNum
let selWith = mkMutationOutputExp qt allCols Nothing selCTE mutationOutput strfyNum
-- Perform select query and fetch returning fields
encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith) [] True
Expand Down Expand Up @@ -108,8 +108,7 @@ mkSelCTEFromColVals qt allCols colVals =
where
rowAlias = Iden "row"
extractor = S.selectStar' $ S.QualIden rowAlias $ Just $ S.TypeAnn $ toSQLTxt qt
sortedCols = flip sortBy allCols $ \lCol rCol ->
compare (pgiPosition lCol) (pgiPosition rCol)
sortedCols = sortCols allCols
mkTupsFromColVal colVal =
fmap S.TupleExp $ forM sortedCols $ \ci -> do
let pgCol = pgiColumn ci
Expand Down
86 changes: 62 additions & 24 deletions server/src-lib/Hasura/RQL/DML/Returning.hs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,8 @@ mkDefaultMutFlds = MOutMultirowFields . \case
where
mutFlds = [("affected_rows", MCount)]

qualTableToAliasIden :: QualifiedTable -> Iden
qualTableToAliasIden qt =
Iden $ snakeCaseTable qt <> "__mutation_result_alias"

mkMutFldExp :: QualifiedTable -> Maybe Int -> Bool -> MutFld -> S.SQLExp
mkMutFldExp qt preCalAffRows strfyNum = \case
mkMutFldExp :: Iden -> Maybe Int -> Bool -> MutFld -> S.SQLExp
mkMutFldExp cteAlias preCalAffRows strfyNum = \case
MCount ->
let countExp = S.SESelect $
S.mkSelect
Expand All @@ -112,28 +108,70 @@ mkMutFldExp qt preCalAffRows strfyNum = \case
tabPerm = TablePerm annBoolExpTrue Nothing
in S.SESelect $ mkSQLSelect JASMultipleRows $
AnnSelG selFlds tabFrom tabPerm noTableArgs strfyNum
where
cteAlias = qualTableToAliasIden qt

{- Note [Mutation output expression]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An example output expression for INSERT mutation:

WITH "<table-name>__mutation_result_alias" AS (
INSERT INTO <table-name> (<insert-column>[..])
VALUES
(<insert-value-row>[..])
ON CONFLICT ON CONSTRAINT "<table-constraint-name>" DO NOTHING RETURNING *,
-- An extra column expression which performs the 'CHECK' validation
CASE
WHEN (<CHECK Condition>) THEN NULL
ELSE "hdb_catalog"."check_violation"('insert check constraint failed')
END
),
"<table-name>__all_columns_alias" AS (
-- Only extract columns from mutated rows. Columns sorted by ordinal position so that
-- resulted rows can be casted to table type.
SELECT (<table-column>[..])
FROM
"<table-name>__mutation_result_alias"
)
<SELECT statement to generate mutation response using '<table-name>__all_columns_alias' as FROM>
-}

-- | Generate mutation output expression with given mutation CTE statement.
-- See Note [Mutation output expression].
mkMutationOutputExp
:: QualifiedTable -> Maybe Int -> S.CTE -> MutationOutput -> Bool -> S.SelectWith
mkMutationOutputExp qt preCalAffRows cte mutOutput strfyNum =
S.SelectWith [(S.Alias cteAlias, cte)] sel
:: QualifiedTable
-> [PGColumnInfo]
-> Maybe Int
-> S.CTE
-> MutationOutput
-> Bool
-> S.SelectWith
mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
S.SelectWith [ (S.Alias mutationResultAlias, cte)
, (S.Alias allColumnsAlias, allColumnsSelect)
] sel
where
cteAlias = qualTableToAliasIden qt
sel = S.mkSelect { S.selExtr = [S.Extractor extrExp Nothing] }
mutationResultAlias = Iden $ snakeCaseQualObject qt <> "__mutation_result_alias"
allColumnsAlias = Iden $ snakeCaseQualObject qt <> "__all_columns_alias"

extrExp = case mutOutput of
MOutMultirowFields mutFlds ->
let jsonBuildObjArgs = flip concatMap mutFlds $
\(FieldName k, mutFld) -> [S.SELit k, mkMutFldExp qt preCalAffRows strfyNum mutFld]
in S.SEFnApp "json_build_object" jsonBuildObjArgs Nothing

MOutSinglerowObject annFlds ->
let tabFrom = FromIden cteAlias
tabPerm = TablePerm annBoolExpTrue Nothing
in S.SESelect $ mkSQLSelect JASSingleObject $
AnnSelG annFlds tabFrom tabPerm noTableArgs strfyNum
allColumnsSelect = S.CTESelect $ S.mkSelect
{ S.selExtr = map S.mkExtr $ map pgiColumn $ sortCols allCols
, S.selFrom = Just $ S.mkIdenFromExp mutationResultAlias
}

sel = S.mkSelect { S.selExtr = [S.Extractor extrExp Nothing] }
where
extrExp = case mutOutput of
MOutMultirowFields mutFlds ->
let jsonBuildObjArgs = flip concatMap mutFlds $
\(FieldName k, mutFld) -> [ S.SELit k
, mkMutFldExp allColumnsAlias preCalAffRows strfyNum mutFld
]
in S.SEFnApp "json_build_object" jsonBuildObjArgs Nothing

MOutSinglerowObject annFlds ->
let tabFrom = FromIden allColumnsAlias
tabPerm = TablePerm annBoolExpTrue Nothing
in S.SESelect $ mkSQLSelect JASSingleObject $
AnnSelG annFlds tabFrom tabPerm noTableArgs strfyNum


checkRetCols
Expand Down
2 changes: 1 addition & 1 deletion server/src-lib/Hasura/RQL/GBoolExp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ convColRhs tableQual = \case
curVarNum <- get
put $ curVarNum + 1
let newIden = Iden $ "_be_" <> T.pack (show curVarNum) <> "_"
<> snakeCaseTable relTN
<> snakeCaseQualObject relTN
newIdenQ = S.QualIden newIden Nothing
annRelBoolExp <- convBoolRhs' newIdenQ nesAnn
let backCompExp = foldr (S.BEBin S.AndOp) (S.BELit True) $
Expand Down
5 changes: 5 additions & 0 deletions server/src-lib/Hasura/RQL/Types/Table.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ module Hasura.RQL.Types.Table
, getFieldInfoM
, getPGColumnInfoM
, getCols
, sortCols
, getRels
, getComputedFieldInfos

Expand Down Expand Up @@ -193,6 +194,10 @@ fieldInfoGraphQLNames = \case
getCols :: FieldInfoMap FieldInfo -> [PGColumnInfo]
getCols = mapMaybe (^? _FIColumn) . M.elems

-- | Sort columns based on their ordinal position
sortCols :: [PGColumnInfo] -> [PGColumnInfo]
sortCols = sortBy (\l r -> compare (pgiPosition l) (pgiPosition r))

getRels :: FieldInfoMap FieldInfo -> [RelInfo]
getRels = mapMaybe (^? _FIRelationship) . M.elems

Expand Down
5 changes: 0 additions & 5 deletions server/src-lib/Hasura/SQL/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ module Hasura.SQL.Types
, isView

, QualifiedTable
, snakeCaseTable
, QualifiedFunction

, PGDescription(..)
Expand Down Expand Up @@ -300,10 +299,6 @@ snakeCaseQualObject (QualifiedObject sn o)

type QualifiedTable = QualifiedObject TableName

snakeCaseTable :: QualifiedObject TableName -> T.Text
snakeCaseTable (QualifiedObject sn tn) =
getSchemaTxt sn <> "_" <> getTableTxt tn

type QualifiedFunction = QualifiedObject FunctionName

newtype PGDescription
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ query:
id
name
is_registered
get_articles(args: {search: "Article"}){
id
title
content
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,31 @@ args:
author_id INTEGER REFERENCES author(id),
is_published BOOLEAN,
published_on TIMESTAMP
)
);

CREATE FUNCTION fetch_articles(search text, author_row author)
RETURNS SETOF article AS $$
SELECT *
FROM article
WHERE
( title ilike ('%' || search || '%')
OR content ilike ('%' || search || '%')
) AND author_id = author_row.id
$$ LANGUAGE sql STABLE;

- type: track_table
args:
schema: public
name: article

- type: add_computed_field
args:
table: author
name: get_articles
definition:
function: fetch_articles
table_argument: author_row

#Create resident table
- type: run_sql
args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ args:
sql: |
drop table address;
drop table resident;
drop function fetch_articles(text,author);
drop table article;
drop table blog;
drop table author;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,30 @@ args:
is_published BOOLEAN,
published_on TIMESTAMP,
PRIMARY KEY (id,version)
)
);

CREATE FUNCTION fetch_articles(search text, author_row author)
RETURNS SETOF article AS $$
SELECT *
FROM article
WHERE
( title ilike ('%' || search || '%')
OR content ilike ('%' || search || '%')
) AND author_id = author_row.id
$$ LANGUAGE sql STABLE;

- type: track_table
args:
schema: public
name: article

- type: add_computed_field
args:
table: author
name: get_articles
definition:
function: fetch_articles
table_argument: author_row

#Object relationship
- type: create_object_relationship
Expand Down Expand Up @@ -77,6 +95,17 @@ args:
filter:
id: X-HASURA-USER-ID

#Author update permission for user
- type: create_update_permission
args:
table: author
role: user
permission:
columns:
- name
filter:
id: X-Hasura-User-Id

#Article select permission for user
- type: create_select_permission
args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ args:
- type: run_sql
args:
sql: |
DROP function fetch_articles(text,author);
DROP table article;
DROP table author;
DROP table person;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
description: Update name of an author
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
response:
data:
update_author:
affected_rows: 1
returning:
- id: 1
name: Author 1 updated
get_articles:
- id: 2
title: Article 1
content: Sample article 1, content version 1.0.1
query:
query: |
mutation {
update_author(
_set: {name: "Author 1 updated"}
where: {id: {_eq: 1}}
){
affected_rows
returning{
id
name
get_articles(args: {search: "1.0.1"}){
id
title
content
}
}
}
}
11 changes: 10 additions & 1 deletion server/tests-py/test_graphql_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ def test_user_err_unexpected_constraint(self, hge_ctx, transport):
def test_role_has_no_permissions_err(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/address_permission_error.yaml")

# This test captures a bug in the previous release
# Avoiding this test for server upgrades
@pytest.mark.skip_server_upgrade_test
def test_author_user_role_insert_check_perm_success(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/author_user_role_insert_check_perm_success.yaml")

Expand Down Expand Up @@ -380,6 +383,12 @@ def dir(cls):
@use_mutation_fixtures
class TestGraphqlUpdatePermissions:

# This test captures a bug in the previous release
# Avoiding this test for server upgrades
@pytest.mark.skip_server_upgrade_test
def test_user_update_author(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/user_update_author.yaml")

def test_user_can_update_unpublished_article(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article.yaml")

Expand All @@ -388,7 +397,7 @@ def test_user_cannot_update_published_version_col(self, hge_ctx, transport):

def test_user_cannot_update_another_users_article(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article.yaml")

def test_user_cannot_publish(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + "/user_cannot_publish.yaml")

Expand Down