diff --git a/age--1.5.0--y.y.y.sql b/age--1.5.0--y.y.y.sql index 23287a852..0239d439e 100644 --- a/age--1.5.0--y.y.y.sql +++ b/age--1.5.0--y.y.y.sql @@ -88,3 +88,13 @@ DEFAULT FOR TYPE agtype USING gin AS FUNCTION 6 ag_catalog.gin_triconsistent_agtype(internal, int2, agtype, int4, internal, internal, internal), STORAGE text; + +-- this function went from variadic "any" to just "any" type +CREATE OR REPLACE FUNCTION ag_catalog.age_tostring("any") + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out index 66344c6ea..b3750bcc4 100644 --- a/regress/expected/agtype.out +++ b/regress/expected/agtype.out @@ -3139,7 +3139,7 @@ SELECT agtype_to_int4_array(NULL); -- --Invalid Map Key (should fail) SELECT agtype_build_map('[0]'::agtype, null); -ERROR: key value must be scalar, not array, composite, or json +ERROR: agtype_build_map_as_agtype_value only supports scalar arguments -- -- Test agtype object/array access operators object.property, object["property"], and array[element] -- Note: At this point, object.property and object["property"] are equivalent. @@ -3907,9 +3907,51 @@ SELECT ag_catalog.agtype_volatile_wrapper(32768::int2); ERROR: smallint out of range SELECT ag_catalog.agtype_volatile_wrapper(-32768::int2); ERROR: smallint out of range +-- +-- test that age_tostring can handle an UNKNOWNOID type +-- +SELECT age_tostring('a'); + age_tostring +-------------- + "a" +(1 row) + +-- +-- test agtype_build_map_as_agtype_value via agtype_build_map +-- +SELECT * FROM create_graph('agtype_build_map'); +NOTICE: graph "agtype_build_map" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('agtype_build_map', $$ RETURN ag_catalog.agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71) + $$) AS (results agtype); + results +--------------------------------------------- + {"1": "1", "2": 2, "e": 2.71, "3.14": 3.14} +(1 row) + +SELECT agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71); + agtype_build_map +--------------------------------------------------------------- + {"1": "1", "2": 2, "e": 2.71::numeric, "3.14": 3.14::numeric} +(1 row) + -- -- Cleanup -- +SELECT drop_graph('agtype_build_map', true); +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table agtype_build_map._ag_label_vertex +drop cascades to table agtype_build_map._ag_label_edge +NOTICE: graph "agtype_build_map" has been dropped + drop_graph +------------ + +(1 row) + DROP TABLE agtype_table; -- -- End of AGTYPE data type regression tests diff --git a/regress/sql/agtype.sql b/regress/sql/agtype.sql index 4089d524c..9e8d44e62 100644 --- a/regress/sql/agtype.sql +++ b/regress/sql/agtype.sql @@ -1098,9 +1098,25 @@ SELECT ag_catalog.agtype_volatile_wrapper(-32767::int2); -- These should fail SELECT ag_catalog.agtype_volatile_wrapper(32768::int2); SELECT ag_catalog.agtype_volatile_wrapper(-32768::int2); + +-- +-- test that age_tostring can handle an UNKNOWNOID type +-- +SELECT age_tostring('a'); + +-- +-- test agtype_build_map_as_agtype_value via agtype_build_map +-- +SELECT * FROM create_graph('agtype_build_map'); +SELECT * FROM cypher('agtype_build_map', $$ RETURN ag_catalog.agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71) + $$) AS (results agtype); +SELECT agtype_build_map('1', '1', 2, 2, 3.14, 3.14, 'e', 2.71); + -- -- Cleanup -- +SELECT drop_graph('agtype_build_map', true); + DROP TABLE agtype_table; -- diff --git a/sql/age_scalar.sql b/sql/age_scalar.sql index 77f7689a7..5014fbbe8 100644 --- a/sql/age_scalar.sql +++ b/sql/age_scalar.sql @@ -149,7 +149,7 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; -CREATE FUNCTION ag_catalog.age_tostring(variadic "any") +CREATE FUNCTION ag_catalog.age_tostring("any") RETURNS agtype LANGUAGE c IMMUTABLE diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 9de22c71c..9fd0bb860 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -176,6 +176,7 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, int min_num_args); static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo); agtype_value *agtype_composite_to_agtype_value_binary(agtype *a); +static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr); /* global storage of OID for agtype and _agtype */ static Oid g_AGTYPEOID = InvalidOid; @@ -2379,6 +2380,7 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_OBJECT, NULL); + /* iterate through the arguments and build the object */ for (i = 0; i < nargs; i += 2) { /* process key */ @@ -2389,7 +2391,25 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) errmsg("argument %d: key must not be null", i + 1))); } - add_agtype(args[i], false, &result, types[i], true); + /* + * If the key is agtype, we need to extract it as an agtype string and + * push the value. + */ + if (types[i] == AGTYPEOID) + { + agtype_value *agtv = NULL; + + agtv = tostring_helper(args[i], types[i], + "agtype_build_map_as_agtype_value"); + result.res = push_agtype_value(&result.parse_state, WAGT_KEY, agtv); + + /* free the agtype_value from tostring_helper */ + pfree(agtv); + } + else + { + add_agtype(args[i], false, &result, types[i], true); + } /* process value */ add_agtype(args[i + 1], nulls[i + 1], &result, types[i + 1], false); @@ -6748,16 +6768,12 @@ PG_FUNCTION_INFO_V1(age_tostring); Datum age_tostring(PG_FUNCTION_ARGS) { int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; - agtype_value agtv_result; - char *string = NULL; - Oid type; + Oid type = InvalidOid; + agtype *agt = NULL; + agtype_value *agtv = NULL; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = PG_NARGS(); /* check number of args */ if (nargs > 1) @@ -6767,19 +6783,70 @@ Datum age_tostring(PG_FUNCTION_ARGS) } /* check for null */ - if (nargs < 0 || nulls[0]) + if (nargs < 1 || PG_ARGISNULL(0)) { PG_RETURN_NULL(); } + /* get the argument and type */ + arg = PG_GETARG_DATUM(0); + type = get_fn_expr_argtype(fcinfo->flinfo, 0); + + /* verify that if the type is UNKNOWNOID it can be converted */ + if (type == UNKNOWNOID && !get_fn_expr_arg_stable(fcinfo->flinfo, 0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("toString() UNKNOWNOID and not stable"))); + } + /* * toString() supports integer, float, numeric, text, cstring, boolean, * regtype or the agtypes: integer, float, numeric, string, boolean input */ - arg = args[0]; - type = types[0]; + agtv = tostring_helper(arg, type, "toString()"); - if (type != AGTYPEOID) + /* if we get a NULL back we need to return NULL */ + if (agtv == NULL) + { + PG_RETURN_NULL(); + } + + /* convert to agtype and free the agtype_value */ + agt = agtype_value_to_agtype(agtv); + pfree(agtv); + + PG_RETURN_POINTER(agt); +} + +/* + * Helper function to take any valid type and convert it to an agtype string. + * Returns NULL for NULL output. + */ +static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) +{ + agtype_value *agtv_result = NULL; + char *string = NULL; + + agtv_result = palloc0(sizeof(agtype_value)); + + /* + * toString() supports: unknown, integer, float, numeric, text, cstring, + * boolean, regtype or the agtypes: integer, float, numeric, string, and + * boolean input. + */ + + /* + * If the type is UNKNOWNOID convert it to a cstring. Prior to passing an + * UNKNOWNOID it should be verified to be stable. + */ + if (type == UNKNOWNOID) + { + char *str = DatumGetPointer(arg); + + string = pnstrdup(str, strlen(str)); + } + /* if it is not an AGTYPEOID */ + else if (type != AGTYPEOID) { if (type == INT2OID) { @@ -6826,11 +6893,12 @@ Datum age_tostring(PG_FUNCTION_ARGS) else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toString() unsupported argument type %d", - type))); + errmsg("%s unsupported argument type %d", + msghdr, type))); } } - else + /* if it is an AGTYPEOID */ + else if (type == AGTYPEOID) { agtype *agt_arg; agtype_value *agtv_value; @@ -6841,14 +6909,15 @@ Datum age_tostring(PG_FUNCTION_ARGS) if (!AGT_ROOT_IS_SCALAR(agt_arg)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toString() only supports scalar arguments"))); + errmsg("%s only supports scalar arguments", + msghdr))); } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); if (agtv_value->type == AGTV_NULL) { - PG_RETURN_NULL(); + return NULL; } else if (agtv_value->type == AGTV_INTEGER) { @@ -6877,17 +6946,24 @@ Datum age_tostring(PG_FUNCTION_ARGS) else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toString() unsupported argument agtype %d", - agtv_value->type))); + errmsg("%s unsupported argument agtype %d", + msghdr, agtv_value->type))); } } + /* it is an unknown type */ + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s unknown argument agtype %d", + msghdr, type))); + } /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = strlen(string); + agtv_result->type = AGTV_STRING; + agtv_result->val.string.val = string; + agtv_result->val.string.len = strlen(string); - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + return agtv_result; } PG_FUNCTION_INFO_V1(age_tostringlist);