Skip to content

Commit ff2645a

Browse files
author
Nikita Glukhov
committed
Add subtransactions for JsonExpr execution
1 parent 6f4a00b commit ff2645a

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
lines changed

src/backend/executor/execExprInterp.c

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
#include "postgres.h"
5858

5959
#include "access/tuptoaster.h"
60+
#include "access/xact.h"
61+
#include "catalog/pg_proc.h"
6062
#include "catalog/pg_type.h"
6163
#include "commands/sequence.h"
6264
#include "executor/execExpr.h"
@@ -77,6 +79,7 @@
7779
#include "utils/jsonb.h"
7880
#include "utils/jsonpath.h"
7981
#include "utils/lsyscache.h"
82+
#include "utils/resowner.h"
8083
#include "utils/timestamp.h"
8184
#include "utils/typcache.h"
8285
#include "utils/xml.h"
@@ -4448,6 +4451,12 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
44484451
return res;
44494452
}
44504453

4454+
bool
4455+
ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr)
4456+
{
4457+
return jsexpr->on_error.btype != JSON_BEHAVIOR_ERROR;
4458+
}
4459+
44514460
/* ----------------------------------------------------------------
44524461
* ExecEvalJson
44534462
* ----------------------------------------------------------------
@@ -4487,20 +4496,43 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
44874496
var->evaluated = false;
44884497
}
44894498

4490-
if (jexpr->on_error.btype == JSON_BEHAVIOR_ERROR)
4499+
if (!ExecEvalJsonNeedsSubTransaction(jexpr))
44914500
{
44924501
/* No need to use PG_TRY/PG_CATCH with subtransactions. */
44934502
res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item,
44944503
op->resnull);
44954504
}
44964505
else
44974506
{
4507+
/*
4508+
* We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
4509+
* execute corresponding ON ERROR behavior.
4510+
*/
44984511
MemoryContext oldcontext = CurrentMemoryContext;
4512+
ResourceOwner oldowner = CurrentResourceOwner;
4513+
ExprContext *newecontext;
4514+
4515+
BeginInternalSubTransaction(NULL);
4516+
/* Want to execute expressions inside function's memory context */
4517+
MemoryContextSwitchTo(oldcontext);
4518+
/*
4519+
* We need to execute expressions with a new econtext
4520+
* that belongs to the current subtransaction; if we try to use
4521+
* the outer econtext then ExprContext shutdown callbacks will be
4522+
* called at the wrong times.
4523+
*/
4524+
newecontext = CreateExprContext(econtext->ecxt_estate);
44994525

45004526
PG_TRY();
45014527
{
4502-
res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item,
4528+
res = ExecEvalJsonExpr(state, op, newecontext, jexpr, path, item,
45034529
op->resnull);
4530+
4531+
/* Commit the inner transaction, return to outer xact context */
4532+
ReleaseCurrentSubTransaction();
4533+
MemoryContextSwitchTo(oldcontext);
4534+
CurrentResourceOwner = oldowner;
4535+
FreeExprContext(newecontext, true);
45044536
}
45054537
PG_CATCH();
45064538
{
@@ -4511,6 +4543,12 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
45114543
edata = CopyErrorData();
45124544
FlushErrorState();
45134545

4546+
/* Abort the inner transaction */
4547+
RollbackAndReleaseCurrentSubTransaction();
4548+
MemoryContextSwitchTo(oldcontext);
4549+
CurrentResourceOwner = oldowner;
4550+
FreeExprContext(newecontext, false);
4551+
45144552
if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != ERRCODE_DATA_EXCEPTION)
45154553
ReThrowError(edata);
45164554

src/backend/optimizer/util/clauses.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "catalog/pg_type.h"
2929
#include "executor/executor.h"
3030
#include "executor/functions.h"
31+
#include "executor/execExpr.h"
3132
#include "funcapi.h"
3233
#include "miscadmin.h"
3334
#include "nodes/makefuncs.h"
@@ -1288,6 +1289,16 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
12881289
context, 0);
12891290
}
12901291

1292+
/* JsonExpr is parallel-unsafe if subtransactions can be used. */
1293+
else if (IsA(node, JsonExpr))
1294+
{
1295+
JsonExpr *jsexpr = (JsonExpr *) node;
1296+
1297+
if (ExecEvalJsonNeedsSubTransaction(jsexpr))
1298+
context->max_hazard = PROPARALLEL_UNSAFE;
1299+
return true;
1300+
}
1301+
12911302
/* Recurse to check arguments */
12921303
return expression_tree_walker(node,
12931304
max_parallel_hazard_walker,

src/include/executor/execExpr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
790790
JsonReturning *returning,
791791
struct JsonCoercionsState *coercions,
792792
struct JsonCoercionState **pjcstate);
793+
extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr);
793794

794795
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup);
795796
extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,

src/test/regress/expected/jsonb_sqljson.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,3 +903,40 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
903903
SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
904904
ERROR: bad jsonpath representation
905905
DETAIL: syntax error, unexpected IDENT_P at or near " "
906+
-- Test parallel JSON_VALUE()
907+
CREATE TABLE test_parallel_jsonb_value AS
908+
SELECT i::text::jsonb AS js
909+
FROM generate_series(1, 1000000) i;
910+
-- Should be non-parallel due to subtransactions
911+
EXPLAIN (COSTS OFF)
912+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
913+
QUERY PLAN
914+
---------------------------------------------
915+
Aggregate
916+
-> Seq Scan on test_parallel_jsonb_value
917+
(2 rows)
918+
919+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
920+
sum
921+
--------------
922+
500000500000
923+
(1 row)
924+
925+
-- Should be parallel
926+
EXPLAIN (COSTS OFF)
927+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
928+
QUERY PLAN
929+
------------------------------------------------------------------
930+
Finalize Aggregate
931+
-> Gather
932+
Workers Planned: 2
933+
-> Partial Aggregate
934+
-> Parallel Seq Scan on test_parallel_jsonb_value
935+
(5 rows)
936+
937+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
938+
sum
939+
--------------
940+
500000500000
941+
(1 row)
942+

src/test/regress/sql/jsonb_sqljson.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,19 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
273273
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
274274
-- Should fail (invalid path)
275275
SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
276+
277+
-- Test parallel JSON_VALUE()
278+
CREATE TABLE test_parallel_jsonb_value AS
279+
SELECT i::text::jsonb AS js
280+
FROM generate_series(1, 1000000) i;
281+
282+
-- Should be non-parallel due to subtransactions
283+
EXPLAIN (COSTS OFF)
284+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
285+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
286+
287+
-- Should be parallel
288+
EXPLAIN (COSTS OFF)
289+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
290+
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
291+

0 commit comments

Comments
 (0)