Skip to content

Commit 71ae144

Browse files
Antoine Leblanchasura-bot
authored andcommitted
server: implement transaction workaround
GitOrigin-RevId: 404f8bb
1 parent 7f420ad commit 71ae144

File tree

16 files changed

+479
-173
lines changed

16 files changed

+479
-173
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
## Next release
44
(Add entries here in the order of: server, console, cli, docs, others)
55

6+
### Transactions for Postgres mutations
7+
8+
With v2 came the introduction of heterogeneous execution: in one query or mutation, you can target different sources: it is possible, for instance, in one mutation, to both insert a row in a table in a table on Postgres and another row in another table on MSSQL:
9+
10+
```
11+
mutation {
12+
// goes to Postgres
13+
insert_author_one(object: {name: "Simon Peyton Jones"}) {
14+
name
15+
}
16+
17+
// goes to MSSQL
18+
insert_publication_one(object: {name: "Template meta-programming for Haskell"}) {
19+
name
20+
}
21+
}
22+
```
23+
24+
However, heterogeneous execution has a cost: we can no longer run mutations as a transaction, given that each part may target a different database. This is a regression compared to v1.
25+
26+
While we want to fix this by offering, in the future, an explicit API that allows our users to *choose* when a series of mutations are executed as a transaction, for now we are introducing the following optimisation: when all the fields in a mutation target the same Postgres source, we will run them as a transaction like we would have in v1.
27+
28+
29+
### Bug fixes and improvements
30+
631
- server: add `--async-actions-fetch-interval` command-line flag and `HASURA_GRAPHQL_ASYNC_ACTIONS_FETCH_INTERVAL` environment variable for configuring
732
async actions re-fetch interval from metadata storage (fix #6460)
833
- server: add 'replace_configuration' option (default: false) in the add source API payload

server/src-lib/Hasura/Backends/MSSQL/Instances/Execute.hs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,19 @@ msDBQueryPlan
5757
-> [HTTP.Header]
5858
-> UserInfo
5959
-> [G.Directive G.Name]
60+
-> SourceName
6061
-> SourceConfig 'MSSQL
6162
-> QueryDB 'MSSQL (UnpreparedValue 'MSSQL)
6263
-> m ExecutionStep
63-
msDBQueryPlan _env _manager _reqHeaders userInfo _directives sourceConfig qrf = do
64+
msDBQueryPlan _env _manager _reqHeaders userInfo _directives sourceName sourceConfig qrf = do
6465
select <- fromSelect <$> planNoPlan userInfo qrf
6566
let queryString = ODBC.renderQuery $ toQueryPretty select
6667
pool = _mscConnectionPool sourceConfig
6768
odbcQuery = encJFromText <$> runJSONPathQuery pool (toQueryFlat select)
6869
pure
6970
$ ExecStepDB []
7071
. AB.mkAnyBackend
71-
$ DBStepInfo sourceConfig (Just queryString) odbcQuery
72+
$ DBStepInfo sourceName sourceConfig (Just queryString) odbcQuery
7273

7374
-- mutation
7475

@@ -81,10 +82,11 @@ msDBMutationPlan
8182
-> [HTTP.Header]
8283
-> UserInfo
8384
-> Bool
85+
-> SourceName
8486
-> SourceConfig 'MSSQL
8587
-> MutationDB 'MSSQL (UnpreparedValue 'MSSQL)
8688
-> m ExecutionStep
87-
msDBMutationPlan _env _manager _reqHeaders _userInfo _stringifyNum _sourceConfig _mrf =
89+
msDBMutationPlan _env _manager _reqHeaders _userInfo _stringifyNum _sourceName _sourceConfig _mrf =
8890
throw500 "mutations are not supported in MSSQL; this should be unreachable"
8991

9092

@@ -95,10 +97,11 @@ msDBSubscriptionPlan
9597
( MonadError QErr m
9698
)
9799
=> UserInfo
100+
-> SourceName
98101
-> SourceConfig 'MSSQL
99102
-> InsOrdHashMap G.Name (QueryDB 'MSSQL (UnpreparedValue 'MSSQL))
100103
-> m (LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL))
101-
msDBSubscriptionPlan userInfo sourceConfig rootFields = do
104+
msDBSubscriptionPlan userInfo _sourceName sourceConfig rootFields = do
102105
-- WARNING: only keeping the first root field for now!
103106
query <- traverse mkQuery $ head $ OMap.toList rootFields
104107
let roleName = _uiRole userInfo

server/src-lib/Hasura/Backends/Postgres/Instances/Execute.hs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,18 @@ pgDBQueryPlan
5858
-> [HTTP.Header]
5959
-> UserInfo
6060
-> [G.Directive G.Name]
61+
-> SourceName
6162
-> SourceConfig 'Postgres
6263
-> QueryDB 'Postgres (UnpreparedValue 'Postgres)
6364
-> m ExecutionStep
64-
pgDBQueryPlan env manager reqHeaders userInfo _directives sourceConfig qrf = do
65+
pgDBQueryPlan env manager reqHeaders userInfo _directives sourceName sourceConfig qrf = do
6566
(preparedQuery, PlanningSt _ _ planVals expectedVariables) <- flip runStateT initPlanningSt $ traverseQueryDB prepareWithPlan qrf
6667
validateSessionVariables expectedVariables $ _uiSession userInfo
6768
let (action, preparedSQL) = mkCurPlanTx env manager reqHeaders userInfo $ irToRootFieldPlan planVals preparedQuery
6869
pure
6970
$ ExecStepDB []
7071
. AB.mkAnyBackend
71-
$ DBStepInfo sourceConfig preparedSQL action
72+
$ DBStepInfo sourceName sourceConfig preparedSQL action
7273

7374

7475
-- mutation
@@ -164,10 +165,11 @@ pgDBMutationPlan
164165
-> [HTTP.Header]
165166
-> UserInfo
166167
-> Bool
168+
-> SourceName
167169
-> SourceConfig 'Postgres
168170
-> MutationDB 'Postgres (UnpreparedValue 'Postgres)
169171
-> m ExecutionStep
170-
pgDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceConfig mrf =
172+
pgDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceName sourceConfig mrf =
171173
go <$> case mrf of
172174
MDBInsert s -> convertInsert env userSession remoteJoinCtx s stringifyNum
173175
MDBUpdate s -> convertUpdate env userSession remoteJoinCtx s stringifyNum
@@ -176,7 +178,8 @@ pgDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceConfig mrf =
176178
where
177179
userSession = _uiSession userInfo
178180
remoteJoinCtx = (manager, reqHeaders, userInfo)
179-
go = ExecStepDB [] . AB.mkAnyBackend . DBStepInfo sourceConfig Nothing
181+
go = ExecStepDB [] . AB.mkAnyBackend . DBStepInfo sourceName sourceConfig Nothing
182+
180183

181184
-- subscription
182185

@@ -186,10 +189,11 @@ pgDBSubscriptionPlan
186189
, MonadIO m
187190
)
188191
=> UserInfo
192+
-> SourceName
189193
-> SourceConfig 'Postgres
190194
-> InsOrdHashMap G.Name (QueryDB 'Postgres (UnpreparedValue 'Postgres))
191195
-> m (LiveQueryPlan 'Postgres (MultiplexedQuery 'Postgres))
192-
pgDBSubscriptionPlan userInfo sourceConfig unpreparedAST = do
196+
pgDBSubscriptionPlan userInfo _sourceName sourceConfig unpreparedAST = do
193197
(preparedAST, PGL.QueryParametersInfo{..}) <- flip runStateT mempty $
194198
for unpreparedAST $ traverseQueryDB PGL.resolveMultiplexedValue
195199
let multiplexedQuery = PGL.mkMultiplexedQuery preparedAST

server/src-lib/Hasura/Backends/Postgres/Instances/Transport.hs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
{-# OPTIONS_GHC -fno-warn-orphans #-}
22

3-
module Hasura.Backends.Postgres.Instances.Transport () where
3+
module Hasura.Backends.Postgres.Instances.Transport
4+
( runPGMutationTransaction
5+
) where
46

57
import Hasura.Prelude
68

79
import qualified Data.Aeson as J
810
import qualified Data.ByteString as B
11+
import qualified Data.HashMap.Strict.InsOrd as OMap
912
import qualified Database.PG.Query as Q
1013
import qualified Language.GraphQL.Draft.Syntax as G
1114

@@ -84,7 +87,7 @@ runPGMutation reqId query fieldName userInfo logger sourceConfig tx _genSql = d
8487
. runLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite
8588
. withTraceContext ctx
8689
. withUserInfo userInfo
87-
) tx
90+
) tx
8891

8992
runPGSubscription
9093
:: ( MonadIO m
@@ -110,3 +113,32 @@ mkQueryLog gqlQuery fieldName preparedSql requestId =
110113
where
111114
generatedQuery = preparedSql <&> \(EQ.PreparedSql query args _) ->
112115
GeneratedQuery (Q.getQueryText query) (J.toJSON $ pgScalarValueToJson . snd <$> args)
116+
117+
118+
-- ad-hoc transaction optimisation
119+
-- see Note [Backwards-compatible transaction optimisation]
120+
121+
runPGMutationTransaction
122+
:: ( MonadIO m
123+
, MonadError QErr m
124+
, MonadQueryLog m
125+
, MonadTrace m
126+
)
127+
=> RequestId
128+
-> GQLReqUnparsed
129+
-> UserInfo
130+
-> L.Logger L.Hasura
131+
-> SourceConfig 'Postgres
132+
-> InsOrdHashMap G.Name (DBStepInfo 'Postgres)
133+
-> m (DiffTime, InsOrdHashMap G.Name EncJSON)
134+
runPGMutationTransaction reqId query userInfo logger sourceConfig mutations = do
135+
logQueryLog logger $ mkQueryLog query $$(G.litName "transaction") Nothing reqId
136+
ctx <- Tracing.currentContext
137+
withElapsedTime $ do
138+
Tracing.interpTraceT (
139+
liftEitherM . liftIO . runExceptT
140+
. runLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite
141+
. withTraceContext ctx
142+
. withUserInfo userInfo
143+
) $ flip OMap.traverseWithKey mutations \fieldName dbsi ->
144+
trace ("Postgres Mutation for root field " <>> fieldName) $ dbsiAction dbsi

server/src-lib/Hasura/GraphQL/Execute.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ buildSubscriptionPlan userInfo rootFields = do
239239
\(C.SourceConfigWith (sourceConfig :: SourceConfig b) _) -> do
240240
qdbs <- traverse (checkField @b sourceName) allFields
241241
LQP . AB.mkAnyBackend . MultiplexedLiveQueryPlan
242-
<$> EB.mkDBSubscriptionPlan userInfo sourceConfig qdbs
242+
<$> EB.mkDBSubscriptionPlan userInfo sourceName sourceConfig qdbs
243243
pure (sourceName, lqp)
244244

245245
checkField

server/src-lib/Hasura/GraphQL/Execute/Backend.hs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Hasura.GraphQL.Execute.LiveQuery.Plan
2424
import Hasura.GraphQL.Parser hiding (Type)
2525
import Hasura.RQL.IR.RemoteJoin
2626
import Hasura.RQL.Types.Backend
27+
import Hasura.RQL.Types.Common
2728
import Hasura.RQL.Types.Error
2829
import Hasura.RQL.Types.RemoteSchema
2930
import Hasura.SQL.Backend
@@ -55,6 +56,7 @@ class ( Backend b
5556
-> [HTTP.Header]
5657
-> UserInfo
5758
-> [G.Directive G.Name]
59+
-> SourceName
5860
-> SourceConfig b
5961
-> QueryDB b (UnpreparedValue b)
6062
-> m ExecutionStep
@@ -68,6 +70,7 @@ class ( Backend b
6870
-> [HTTP.Header]
6971
-> UserInfo
7072
-> Bool
73+
-> SourceName
7174
-> SourceConfig b
7275
-> MutationDB b (UnpreparedValue b)
7376
-> m ExecutionStep
@@ -77,15 +80,17 @@ class ( Backend b
7780
, MonadIO m
7881
)
7982
=> UserInfo
83+
-> SourceName
8084
-> SourceConfig b
8185
-> InsOrdHashMap G.Name (QueryDB b (UnpreparedValue b))
8286
-> m (LiveQueryPlan b (MultiplexedQuery b))
8387

84-
data DBStepInfo b =
85-
DBStepInfo
86-
(SourceConfig b)
87-
(Maybe (PreparedQuery b))
88-
(ExecutionMonad b EncJSON)
88+
data DBStepInfo b = DBStepInfo
89+
{ dbsiSourceName :: SourceName
90+
, dbsiSourceConfig :: SourceConfig b
91+
, dbsiPreparedQuery :: Maybe (PreparedQuery b)
92+
, dbsiAction :: ExecutionMonad b EncJSON
93+
}
8994

9095
-- | One execution step to processing a GraphQL query (e.g. one root field).
9196
data ExecutionStep where
@@ -119,5 +124,5 @@ getRemoteSchemaInfo
119124
. BackendExecute b
120125
=> DBStepInfo b
121126
-> [RemoteSchemaInfo]
122-
getRemoteSchemaInfo (DBStepInfo _ genSql _) =
127+
getRemoteSchemaInfo (DBStepInfo _ _ genSql _) =
123128
IR._rjRemoteSchema <$> maybe [] (getRemoteJoins @b) genSql

server/src-lib/Hasura/GraphQL/Execute/Mutation.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ convertMutationSelectionSet env logger gqlContext SQLGenCtx{stringifyNum} userIn
8181

8282
-- Transform the RQL AST into a prepared SQL query
8383
txs <- for unpreparedQueries \case
84-
RFDB _ exists ->
84+
RFDB sourceName exists ->
8585
AB.dispatchAnyBackend @BackendExecute exists
8686
\(SourceConfigWith sourceConfig (MDBR db)) ->
87-
mkDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceConfig db
87+
mkDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceName sourceConfig db
8888
RFRemote remoteField -> do
8989
RemoteFieldG remoteSchemaInfo resolvedRemoteField <- resolveRemoteField userInfo remoteField
9090
pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeMutation $ [G.SelectionField resolvedRemoteField]

server/src-lib/Hasura/GraphQL/Execute/Query.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ convertQuerySelSet env logger gqlContext userInfo manager reqHeaders directives
7979
-- Transform the query plans into an execution plan
8080
let usrVars = _uiSession userInfo
8181
executionPlan <- for unpreparedQueries \case
82-
RFDB _ exists ->
82+
RFDB sourceName exists ->
8383
AB.dispatchAnyBackend @BackendExecute exists
8484
\(SourceConfigWith sourceConfig (QDBR db)) ->
85-
mkDBQueryPlan env manager reqHeaders userInfo directives sourceConfig db
85+
mkDBQueryPlan env manager reqHeaders userInfo directives sourceName sourceConfig db
8686
RFRemote rf -> do
8787
RemoteFieldG remoteSchemaInfo remoteField <- for rf $ resolveRemoteVariable userInfo
8888
pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeQuery [G.SelectionField remoteField]

0 commit comments

Comments
 (0)