Skip to content

Commit ba8e3cb

Browse files
committed
Fix composite SRF (RETURNS TABLE) to correctly handle array returns
The composite case in call_srf_function had three bugs: 1. Used argv[0] (input argument) instead of ret (JS return value), causing all column values to be NULL. 2. Did not handle array-of-objects return for RETURNS TABLE/SETOF, only processing a single object. JS functions returning arrays like [{id:1, name:'a'}, {id:2, name:'b'}] would crash (segfault). 3. Missing NULL check on pfree(values) when pljs_jsvalue_to_datums returns NULL. Also fixed a minor memory leak: added JS_FreeValue for array elements in the non-composite scalar array case. Added regression tests for: - RETURNS TABLE with array of objects - RETURNS TABLE with single object return - RETURNS SETOF composite with array return
1 parent 194c4d5 commit ba8e3cb

3 files changed

Lines changed: 112 additions & 7 deletions

File tree

expected/function.out

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,52 @@ SELECT * FROM set_of_unnamed_records() AS x(a int, b int);
239239
1 | 2
240240
(1 row)
241241

242+
-- RETURNS TABLE with array of objects (composite SRF array return)
243+
CREATE FUNCTION returns_table_array() RETURNS TABLE(id int, name text) AS
244+
$$
245+
return [
246+
{ id: 1, name: 'Alice' },
247+
{ id: 2, name: 'Bob' },
248+
{ id: 3, name: 'Charlie' }
249+
];
250+
$$
251+
LANGUAGE pljs;
252+
SELECT * FROM returns_table_array();
253+
id | name
254+
----+---------
255+
1 | Alice
256+
2 | Bob
257+
3 | Charlie
258+
(3 rows)
259+
260+
-- RETURNS TABLE with single object
261+
CREATE FUNCTION returns_table_single() RETURNS TABLE(x int, y text) AS
262+
$$
263+
return { x: 42, y: 'hello' };
264+
$$
265+
LANGUAGE pljs;
266+
SELECT * FROM returns_table_single();
267+
x | y
268+
----+-------
269+
42 | hello
270+
(1 row)
271+
272+
-- RETURNS SETOF composite with array return
273+
CREATE FUNCTION set_of_rec_array() RETURNS SETOF rec AS
274+
$$
275+
return [
276+
{ i: 10, t: 'ten' },
277+
{ i: 20, t: 'twenty' }
278+
];
279+
$$
280+
LANGUAGE pljs;
281+
SELECT * FROM set_of_rec_array();
282+
i | t
283+
----+--------
284+
10 | ten
285+
20 | twenty
286+
(2 rows)
287+
242288
-- execute with an array of arguments
243289
CREATE FUNCTION execute_with_array() RETURNS VOID AS
244290
$$

sql/function.sql

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,37 @@ SELECT * FROM set_of_unnamed_records() AS x(a int, c int);
148148
-- name counts and values match
149149
SELECT * FROM set_of_unnamed_records() AS x(a int, b int);
150150

151+
-- RETURNS TABLE with array of objects (composite SRF array return)
152+
CREATE FUNCTION returns_table_array() RETURNS TABLE(id int, name text) AS
153+
$$
154+
return [
155+
{ id: 1, name: 'Alice' },
156+
{ id: 2, name: 'Bob' },
157+
{ id: 3, name: 'Charlie' }
158+
];
159+
$$
160+
LANGUAGE pljs;
161+
SELECT * FROM returns_table_array();
162+
163+
-- RETURNS TABLE with single object
164+
CREATE FUNCTION returns_table_single() RETURNS TABLE(x int, y text) AS
165+
$$
166+
return { x: 42, y: 'hello' };
167+
$$
168+
LANGUAGE pljs;
169+
SELECT * FROM returns_table_single();
170+
171+
-- RETURNS SETOF composite with array return
172+
CREATE FUNCTION set_of_rec_array() RETURNS SETOF rec AS
173+
$$
174+
return [
175+
{ i: 10, t: 'ten' },
176+
{ i: 20, t: 'twenty' }
177+
];
178+
$$
179+
LANGUAGE pljs;
180+
SELECT * FROM set_of_rec_array();
181+
151182
-- execute with an array of arguments
152183
CREATE FUNCTION execute_with_array() RETURNS VOID AS
153184
$$

src/pljs.c

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,15 +1183,42 @@ static Datum call_srf_function(FunctionCallInfo fcinfo, pljs_context *context,
11831183
bool is_null = false;
11841184

11851185
if (state->is_composite) {
1186-
bool *nulls = (bool *)palloc0(sizeof(bool) * state->tuple_desc->natts);
1186+
// Handle composite (RETURNS TABLE / RETURNS SETOF record):
1187+
// JS can return a single object or an array of objects.
1188+
if (JS_IsArray(context->ctx, ret)) {
1189+
// Array of objects: iterate and put each as a row.
1190+
for (uint32_t i = 0; i < pljs_js_array_length(ret, context->ctx);
1191+
i++) {
1192+
JSValue val = JS_GetPropertyUint32(context->ctx, ret, i);
1193+
bool *nulls =
1194+
(bool *)palloc0(sizeof(bool) * state->tuple_desc->natts);
1195+
1196+
Datum *values = pljs_jsvalue_to_datums(
1197+
NULL, val, &nulls, state->tuple_desc, context->ctx);
1198+
1199+
if (values != NULL) {
1200+
tuplestore_putvalues(state->tuple_store_state,
1201+
state->tuple_desc, values, nulls);
1202+
pfree(values);
1203+
}
1204+
pfree(nulls);
1205+
JS_FreeValue(context->ctx, val);
1206+
}
1207+
} else {
1208+
// Single object: put as one row.
1209+
bool *nulls =
1210+
(bool *)palloc0(sizeof(bool) * state->tuple_desc->natts);
11871211

1188-
Datum *values = pljs_jsvalue_to_datums(NULL, argv[0], &nulls,
1189-
state->tuple_desc, context->ctx);
1190-
tuplestore_putvalues(state->tuple_store_state, state->tuple_desc,
1191-
values, nulls);
1212+
Datum *values = pljs_jsvalue_to_datums(
1213+
NULL, ret, &nulls, state->tuple_desc, context->ctx);
11921214

1193-
pfree(nulls);
1194-
pfree(values);
1215+
if (values != NULL) {
1216+
tuplestore_putvalues(state->tuple_store_state,
1217+
state->tuple_desc, values, nulls);
1218+
pfree(values);
1219+
}
1220+
pfree(nulls);
1221+
}
11951222
} else {
11961223
if (JS_IsArray(context->ctx, ret)) {
11971224
for (uint32_t i = 0; i < pljs_js_array_length(ret, context->ctx);
@@ -1203,6 +1230,7 @@ static Datum call_srf_function(FunctionCallInfo fcinfo, pljs_context *context,
12031230
context->ctx, NULL);
12041231
tuplestore_putvalues(state->tuple_store_state, state->tuple_desc,
12051232
&result, &is_null);
1233+
JS_FreeValue(context->ctx, val);
12061234
}
12071235
} else {
12081236
if (!JS_IsUndefined(ret)) {

0 commit comments

Comments
 (0)