Skip to content

Commit c22d98f

Browse files
committed
Allow agtype_build_map to use AGTYPE keys (apache#1666)
Added the ability for agtype_build_map to use AGTYPE keys. In doing this, age_tostring needed to be broken up and rewritten to handle UNKNOWNOID. This rewrite allows other functions to use the logic in age_tostring without using age_tostring directly. Additionally, rewrote age_tostring as a ("any") type function; it used to be a (variadic "any"). The logic here can be used to re- write other (variadic "any") functions that have a set number of input parameters. Added regression tests.
1 parent 63dac79 commit c22d98f

File tree

5 files changed

+170
-26
lines changed

5 files changed

+170
-26
lines changed

age--1.5.0--y.y.y.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,13 @@ DEFAULT FOR TYPE agtype USING gin AS
107107
FUNCTION 6 ag_catalog.gin_triconsistent_agtype(internal, int2, agtype, int4,
108108
internal, internal, internal),
109109
STORAGE text;
110+
111+
-- this function went from variadic "any" to just "any" type
112+
CREATE OR REPLACE FUNCTION ag_catalog.age_tostring("any")
113+
RETURNS agtype
114+
LANGUAGE c
115+
IMMUTABLE
116+
RETURNS NULL ON NULL INPUT
117+
PARALLEL SAFE
118+
AS 'MODULE_PATHNAME';
119+

regress/expected/agtype.out

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3139,7 +3139,7 @@ SELECT agtype_to_int4_array(NULL);
31393139
--
31403140
--Invalid Map Key (should fail)
31413141
SELECT agtype_build_map('[0]'::agtype, null);
3142-
ERROR: key value must be scalar, not array, composite, or json
3142+
ERROR: agtype_build_map_as_agtype_value only supports scalar arguments
31433143
--
31443144
-- Test agtype object/array access operators object.property, object["property"], and array[element]
31453145
-- Note: At this point, object.property and object["property"] are equivalent.
@@ -3907,9 +3907,51 @@ SELECT ag_catalog.agtype_volatile_wrapper(32768::int2);
39073907
ERROR: smallint out of range
39083908
SELECT ag_catalog.agtype_volatile_wrapper(-32768::int2);
39093909
ERROR: smallint out of range
3910+
--
3911+
-- test that age_tostring can handle an UNKNOWNOID type
3912+
--
3913+
SELECT age_tostring('a');
3914+
age_tostring
3915+
--------------
3916+
"a"
3917+
(1 row)
3918+
3919+
--
3920+
-- test agtype_build_map_as_agtype_value via agtype_build_map
3921+
--
3922+
SELECT * FROM create_graph('agtype_build_map');
3923+
NOTICE: graph "agtype_build_map" has been created
3924+
create_graph
3925+
--------------
3926+
3927+
(1 row)
3928+
3929+
SELECT * FROM cypher('agtype_build_map', $$ RETURN ag_catalog.agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71)
3930+
$$) AS (results agtype);
3931+
results
3932+
---------------------------------------------
3933+
{"1": "1", "2": 2, "e": 2.71, "3.14": 3.14}
3934+
(1 row)
3935+
3936+
SELECT agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71);
3937+
agtype_build_map
3938+
---------------------------------------------------------------
3939+
{"1": "1", "2": 2, "e": 2.71::numeric, "3.14": 3.14::numeric}
3940+
(1 row)
3941+
39103942
--
39113943
-- Cleanup
39123944
--
3945+
SELECT drop_graph('agtype_build_map', true);
3946+
NOTICE: drop cascades to 2 other objects
3947+
DETAIL: drop cascades to table agtype_build_map._ag_label_vertex
3948+
drop cascades to table agtype_build_map._ag_label_edge
3949+
NOTICE: graph "agtype_build_map" has been dropped
3950+
drop_graph
3951+
------------
3952+
3953+
(1 row)
3954+
39133955
DROP TABLE agtype_table;
39143956
--
39153957
-- End of AGTYPE data type regression tests

regress/sql/agtype.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,25 @@ SELECT ag_catalog.agtype_volatile_wrapper(-32767::int2);
10981098
-- These should fail
10991099
SELECT ag_catalog.agtype_volatile_wrapper(32768::int2);
11001100
SELECT ag_catalog.agtype_volatile_wrapper(-32768::int2);
1101+
1102+
--
1103+
-- test that age_tostring can handle an UNKNOWNOID type
1104+
--
1105+
SELECT age_tostring('a');
1106+
1107+
--
1108+
-- test agtype_build_map_as_agtype_value via agtype_build_map
1109+
--
1110+
SELECT * FROM create_graph('agtype_build_map');
1111+
SELECT * FROM cypher('agtype_build_map', $$ RETURN ag_catalog.agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71)
1112+
$$) AS (results agtype);
1113+
SELECT agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71);
1114+
11011115
--
11021116
-- Cleanup
11031117
--
1118+
SELECT drop_graph('agtype_build_map', true);
1119+
11041120
DROP TABLE agtype_table;
11051121

11061122
--

sql/age_scalar.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ RETURNS NULL ON NULL INPUT
149149
PARALLEL SAFE
150150
AS 'MODULE_PATHNAME';
151151

152-
CREATE FUNCTION ag_catalog.age_tostring(variadic "any")
152+
CREATE FUNCTION ag_catalog.age_tostring("any")
153153
RETURNS agtype
154154
LANGUAGE c
155155
IMMUTABLE

src/backend/utils/adt/agtype.c

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo,
175175
int min_num_args);
176176
static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo);
177177
agtype_value *agtype_composite_to_agtype_value_binary(agtype *a);
178+
static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr);
178179

179180
/* global storage of OID for agtype and _agtype */
180181
static Oid g_AGTYPEOID = InvalidOid;
@@ -2378,6 +2379,7 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
23782379
result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_OBJECT,
23792380
NULL);
23802381

2382+
/* iterate through the arguments and build the object */
23812383
for (i = 0; i < nargs; i += 2)
23822384
{
23832385
/* process key */
@@ -2388,7 +2390,25 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
23882390
errmsg("argument %d: key must not be null", i + 1)));
23892391
}
23902392

2391-
add_agtype(args[i], false, &result, types[i], true);
2393+
/*
2394+
* If the key is agtype, we need to extract it as an agtype string and
2395+
* push the value.
2396+
*/
2397+
if (types[i] == AGTYPEOID)
2398+
{
2399+
agtype_value *agtv = NULL;
2400+
2401+
agtv = tostring_helper(args[i], types[i],
2402+
"agtype_build_map_as_agtype_value");
2403+
result.res = push_agtype_value(&result.parse_state, WAGT_KEY, agtv);
2404+
2405+
/* free the agtype_value from tostring_helper */
2406+
pfree(agtv);
2407+
}
2408+
else
2409+
{
2410+
add_agtype(args[i], false, &result, types[i], true);
2411+
}
23922412

23932413
/* process value */
23942414
add_agtype(args[i + 1], nulls[i + 1], &result, types[i + 1], false);
@@ -6709,16 +6729,12 @@ PG_FUNCTION_INFO_V1(age_tostring);
67096729
Datum age_tostring(PG_FUNCTION_ARGS)
67106730
{
67116731
int nargs;
6712-
Datum *args;
67136732
Datum arg;
6714-
bool *nulls;
6715-
Oid *types;
6716-
agtype_value agtv_result;
6717-
char *string = NULL;
6718-
Oid type;
6733+
Oid type = InvalidOid;
6734+
agtype *agt = NULL;
6735+
agtype_value *agtv = NULL;
67196736

6720-
/* extract argument values */
6721-
nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
6737+
nargs = PG_NARGS();
67226738

67236739
/* check number of args */
67246740
if (nargs > 1)
@@ -6728,19 +6744,70 @@ Datum age_tostring(PG_FUNCTION_ARGS)
67286744
}
67296745

67306746
/* check for null */
6731-
if (nargs < 0 || nulls[0])
6747+
if (nargs < 1 || PG_ARGISNULL(0))
67326748
{
67336749
PG_RETURN_NULL();
67346750
}
67356751

6752+
/* get the argument and type */
6753+
arg = PG_GETARG_DATUM(0);
6754+
type = get_fn_expr_argtype(fcinfo->flinfo, 0);
6755+
6756+
/* verify that if the type is UNKNOWNOID it can be converted */
6757+
if (type == UNKNOWNOID && !get_fn_expr_arg_stable(fcinfo->flinfo, 0))
6758+
{
6759+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6760+
errmsg("toString() UNKNOWNOID and not stable")));
6761+
}
6762+
67366763
/*
67376764
* toString() supports integer, float, numeric, text, cstring, boolean,
67386765
* regtype or the agtypes: integer, float, numeric, string, boolean input
67396766
*/
6740-
arg = args[0];
6741-
type = types[0];
6767+
agtv = tostring_helper(arg, type, "toString()");
67426768

6743-
if (type != AGTYPEOID)
6769+
/* if we get a NULL back we need to return NULL */
6770+
if (agtv == NULL)
6771+
{
6772+
PG_RETURN_NULL();
6773+
}
6774+
6775+
/* convert to agtype and free the agtype_value */
6776+
agt = agtype_value_to_agtype(agtv);
6777+
pfree(agtv);
6778+
6779+
PG_RETURN_POINTER(agt);
6780+
}
6781+
6782+
/*
6783+
* Helper function to take any valid type and convert it to an agtype string.
6784+
* Returns NULL for NULL output.
6785+
*/
6786+
static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr)
6787+
{
6788+
agtype_value *agtv_result = NULL;
6789+
char *string = NULL;
6790+
6791+
agtv_result = palloc0(sizeof(agtype_value));
6792+
6793+
/*
6794+
* toString() supports: unknown, integer, float, numeric, text, cstring,
6795+
* boolean, regtype or the agtypes: integer, float, numeric, string, and
6796+
* boolean input.
6797+
*/
6798+
6799+
/*
6800+
* If the type is UNKNOWNOID convert it to a cstring. Prior to passing an
6801+
* UNKNOWNOID it should be verified to be stable.
6802+
*/
6803+
if (type == UNKNOWNOID)
6804+
{
6805+
char *str = DatumGetPointer(arg);
6806+
6807+
string = pnstrdup(str, strlen(str));
6808+
}
6809+
/* if it is not an AGTYPEOID */
6810+
else if (type != AGTYPEOID)
67446811
{
67456812
if (type == INT2OID)
67466813
{
@@ -6787,11 +6854,12 @@ Datum age_tostring(PG_FUNCTION_ARGS)
67876854
else
67886855
{
67896856
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6790-
errmsg("toString() unsupported argument type %d",
6791-
type)));
6857+
errmsg("%s unsupported argument type %d",
6858+
msghdr, type)));
67926859
}
67936860
}
6794-
else
6861+
/* if it is an AGTYPEOID */
6862+
else if (type == AGTYPEOID)
67956863
{
67966864
agtype *agt_arg;
67976865
agtype_value *agtv_value;
@@ -6802,14 +6870,15 @@ Datum age_tostring(PG_FUNCTION_ARGS)
68026870
if (!AGT_ROOT_IS_SCALAR(agt_arg))
68036871
{
68046872
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6805-
errmsg("toString() only supports scalar arguments")));
6873+
errmsg("%s only supports scalar arguments",
6874+
msghdr)));
68066875
}
68076876

68086877
agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0);
68096878

68106879
if (agtv_value->type == AGTV_NULL)
68116880
{
6812-
PG_RETURN_NULL();
6881+
return NULL;
68136882
}
68146883
else if (agtv_value->type == AGTV_INTEGER)
68156884
{
@@ -6838,17 +6907,24 @@ Datum age_tostring(PG_FUNCTION_ARGS)
68386907
else
68396908
{
68406909
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6841-
errmsg("toString() unsupported argument agtype %d",
6842-
agtv_value->type)));
6910+
errmsg("%s unsupported argument agtype %d",
6911+
msghdr, agtv_value->type)));
68436912
}
68446913
}
6914+
/* it is an unknown type */
6915+
else
6916+
{
6917+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6918+
errmsg("%s unknown argument agtype %d",
6919+
msghdr, type)));
6920+
}
68456921

68466922
/* build the result */
6847-
agtv_result.type = AGTV_STRING;
6848-
agtv_result.val.string.val = string;
6849-
agtv_result.val.string.len = strlen(string);
6923+
agtv_result->type = AGTV_STRING;
6924+
agtv_result->val.string.val = string;
6925+
agtv_result->val.string.len = strlen(string);
68506926

6851-
PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
6927+
return agtv_result;
68526928
}
68536929

68546930
PG_FUNCTION_INFO_V1(age_tostringlist);

0 commit comments

Comments
 (0)