From 6f30d0d5d5c188c6ee69b876598a83cbd80df30b Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Fri, 3 Nov 2023 14:29:56 -0700 Subject: [PATCH] Fix Issue 1329 - agtype_to_int4 crash (#1339) (#1346) Fixed issue 1329 where `agtype_to_int`<8,4,2> and `agtype_to_int4_array` crashed due to not properly checking input. As these functions take "any" input, the input has to be properly checked before casting it to a specific type. The input section assumed it was agtype, which caused crashes for non-agtypes. The functions `agtype_to_int`<8,4,2> will convert non-agtypes into agtype. However, there were no regression tests for this. The functions `agtype_to_int`<8,4,2> will convert non-agtypes to agtype ints but, did not for their string equivs. Meaning, passing a ('true') or ('3.14') would fail but, passing a (true) or (3.14) would not. This has been corrected for all 3 functions. TODO - The function `agtype_to_int4_array` only takes agtype, currently, and we should consider allowing it to take "any" types. Added regression tests. Added missing regression tests. Conflicts: regress/expected/agtype.out regress/expected/expr.out src/backend/utils/adt/agtype.c --- regress/expected/agtype.out | 324 ++++++++++++++++++++++++++++++++- regress/expected/expr.out | 59 +++++- regress/sql/agtype.sql | 82 ++++++++- regress/sql/expr.sql | 15 +- src/backend/utils/adt/agtype.c | 320 ++++++++++++++++++++++++++------ 5 files changed, 727 insertions(+), 73 deletions(-) diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out index f1134a36f..103a61ba9 100644 --- a/regress/expected/agtype.out +++ b/regress/expected/agtype.out @@ -2216,6 +2216,91 @@ SELECT agtype_to_int8(agtype_in('false')); 0 (1 row) +-- should return SQL NULL +SELECT agtype_to_int8(agtype_in('null')); + agtype_to_int8 +---------------- + +(1 row) + +SELECT agtype_to_int8(NULL); + agtype_to_int8 +---------------- + +(1 row) + +-- non agtype input +SELECT agtype_to_int8(1); + agtype_to_int8 +---------------- + 1 +(1 row) + +SELECT agtype_to_int8(3.14); + agtype_to_int8 +---------------- + 3 +(1 row) + +SELECT agtype_to_int8(3.14::numeric); + agtype_to_int8 +---------------- + 3 +(1 row) + +SELECT agtype_to_int8('3'); + agtype_to_int8 +---------------- + 3 +(1 row) + +SELECT agtype_to_int8(true); + agtype_to_int8 +---------------- + 1 +(1 row) + +SELECT agtype_to_int8(false); + agtype_to_int8 +---------------- + 0 +(1 row) + +SELECT agtype_to_int8('3.14'); + agtype_to_int8 +---------------- + 3 +(1 row) + +SELECT agtype_to_int8('true'); + agtype_to_int8 +---------------- + 1 +(1 row) + +SELECT agtype_to_int8('false'); + agtype_to_int8 +---------------- + 0 +(1 row) + +-- should fail +SELECT agtype_to_int8('neither'); +ERROR: invalid input syntax for type agtype +DETAIL: Expected agtype value, but found "neither". +CONTEXT: agtype data, line 1: neither +SELECT agtype_to_int8('NaN'); +ERROR: bigint out of range +SELECT agtype_to_int8('Inf'); +ERROR: bigint out of range +SELECT agtype_to_int8(NaN); +ERROR: column "nan" does not exist +LINE 1: SELECT agtype_to_int8(NaN); + ^ +SELECT agtype_to_int8(Inf); +ERROR: column "inf" does not exist +LINE 1: SELECT agtype_to_int8(Inf); + ^ -- -- Test boolean to integer cast -- @@ -2231,14 +2316,8 @@ SELECT agtype_to_int4(agtype_in('false')); 0 (1 row) -SELECT agtype_to_int4(agtype_in('null')); - agtype_to_int4 ----------------- - -(1 row) - -- --- Test agtype to integer cast +-- Test agtype to integer4 cast -- SELECT agtype_to_int4(agtype_in('1')); agtype_to_int4 @@ -2260,11 +2339,228 @@ SELECT agtype_to_int4(agtype_in('1.444::numeric')); -- These should all fail SELECT agtype_to_int4(agtype_in('"string"')); -ERROR: invalid input syntax for integer: "string" +ERROR: invalid input syntax for type agtype +DETAIL: Expected agtype value, but found "string". +CONTEXT: agtype data, line 1: string SELECT agtype_to_int4(agtype_in('[1, 2, 3]')); ERROR: cannot cast agtype array to type int SELECT agtype_to_int4(agtype_in('{"int":1}')); ERROR: cannot cast agtype object to type int +-- should return SQL NULL +SELECT agtype_to_int4(agtype_in('null')); + agtype_to_int4 +---------------- + +(1 row) + +SELECT agtype_to_int4(NULL); + agtype_to_int4 +---------------- + +(1 row) + +-- non agtype input +SELECT agtype_to_int4(1); + agtype_to_int4 +---------------- + 1 +(1 row) + +SELECT agtype_to_int4(3.14); + agtype_to_int4 +---------------- + 3 +(1 row) + +SELECT agtype_to_int4(3.14::numeric); + agtype_to_int4 +---------------- + 3 +(1 row) + +SELECT agtype_to_int4('3'); + agtype_to_int4 +---------------- + 3 +(1 row) + +SELECT agtype_to_int4(true); + agtype_to_int4 +---------------- + 1 +(1 row) + +SELECT agtype_to_int4(false); + agtype_to_int4 +---------------- + 0 +(1 row) + +SELECT agtype_to_int4('3.14'); + agtype_to_int4 +---------------- + 3 +(1 row) + +SELECT agtype_to_int4('true'); + agtype_to_int4 +---------------- + 1 +(1 row) + +SELECT agtype_to_int4('false'); + agtype_to_int4 +---------------- + 0 +(1 row) + +-- should error +SELECT agtype_to_int4('neither'); +ERROR: invalid input syntax for type agtype +DETAIL: Expected agtype value, but found "neither". +CONTEXT: agtype data, line 1: neither +SELECT agtype_to_int4('NaN'); +ERROR: integer out of range +SELECT agtype_to_int4('Inf'); +ERROR: integer out of range +SELECT agtype_to_int4(NaN); +ERROR: column "nan" does not exist +LINE 1: SELECT agtype_to_int4(NaN); + ^ +SELECT agtype_to_int4(Inf); +ERROR: column "inf" does not exist +LINE 1: SELECT agtype_to_int4(Inf); + ^ +-- +-- Test boolean to integer2 cast +-- +SELECT agtype_to_int2(agtype_in('true')); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2(agtype_in('false')); + agtype_to_int2 +---------------- + 0 +(1 row) + +-- +-- Test agtype to integer2 cast +-- +SELECT agtype_to_int2(agtype_in('1')); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2(agtype_in('1.45')); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2(agtype_in('1.444::numeric')); + agtype_to_int2 +---------------- + 1 +(1 row) + +-- These should all fail +SELECT agtype_to_int2(agtype_in('"string"')); +ERROR: invalid input syntax for type agtype +DETAIL: Expected agtype value, but found "string". +CONTEXT: agtype data, line 1: string +SELECT agtype_to_int2(agtype_in('[1, 2, 3]')); +ERROR: cannot cast agtype array to type int +SELECT agtype_to_int2(agtype_in('{"int":1}')); +ERROR: cannot cast agtype object to type int +-- should return SQL NULL +SELECT agtype_to_int2(agtype_in('null')); + agtype_to_int2 +---------------- + +(1 row) + +SELECT agtype_to_int2(NULL); + agtype_to_int2 +---------------- + +(1 row) + +-- non agtype input +SELECT agtype_to_int2(1); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2(3.14); + agtype_to_int2 +---------------- + 3 +(1 row) + +SELECT agtype_to_int2(3.14::numeric); + agtype_to_int2 +---------------- + 3 +(1 row) + +SELECT agtype_to_int2('3'); + agtype_to_int2 +---------------- + 3 +(1 row) + +SELECT agtype_to_int2(true); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2(false); + agtype_to_int2 +---------------- + 0 +(1 row) + +SELECT agtype_to_int2('3.14'); + agtype_to_int2 +---------------- + 3 +(1 row) + +SELECT agtype_to_int2('true'); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int2('false'); + agtype_to_int2 +---------------- + 0 +(1 row) + +-- should error +SELECT agtype_to_int2('neither'); +ERROR: invalid input syntax for type agtype +DETAIL: Expected agtype value, but found "neither". +CONTEXT: agtype data, line 1: neither +SELECT agtype_to_int2('NaN'); +ERROR: smallint out of range +SELECT agtype_to_int2('Inf'); +ERROR: smallint out of range +SELECT agtype_to_int2(NaN); +ERROR: column "nan" does not exist +LINE 1: SELECT agtype_to_int2(NaN); + ^ +SELECT agtype_to_int2(Inf); +ERROR: column "inf" does not exist +LINE 1: SELECT agtype_to_int2(Inf); + ^ -- -- Test agtype to int[] -- @@ -2286,6 +2582,18 @@ SELECT agtype_to_int4_array(agtype_in('["6","7",3.66]')); {6,7,4} (1 row) +-- should error +SELECT agtype_to_int4_array(bool('true')); +ERROR: argument must resolve to agtype +SELECT agtype_to_int4_array((1,2,3,4,5)); +ERROR: argument must resolve to agtype +-- should return SQL NULL +SELECT agtype_to_int4_array(NULL); + agtype_to_int4_array +---------------------- + +(1 row) + -- -- Map Literal -- diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 3ba3be436..7e88001e0 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -1228,11 +1228,14 @@ $$) AS (i int); 1 (1 row) ---Invalid String Format SELECT * FROM cypher('type_coercion', $$ RETURN '1.0' $$) AS (i bigint); -ERROR: invalid input syntax for integer: "1.0" + i +--- + 1 +(1 row) + -- Casting to ints that will cause overflow SELECT * FROM cypher('type_coercion', $$ RETURN 10000000000000000000 @@ -7443,6 +7446,58 @@ SELECT * FROM bool(true AND false); f (1 row) +-- Issue 1329 +-- returns 1 +SELECT agtype_to_int2(bool('true')); + agtype_to_int2 +---------------- + 1 +(1 row) + +SELECT agtype_to_int4(bool('true')); + agtype_to_int4 +---------------- + 1 +(1 row) + +SELECT agtype_to_int8(bool('true')); + agtype_to_int8 +---------------- + 1 +(1 row) + +-- returns 0 +SELECT agtype_to_int2(bool('false')); + agtype_to_int2 +---------------- + 0 +(1 row) + +SELECT agtype_to_int4(bool('false')); + agtype_to_int4 +---------------- + 0 +(1 row) + +SELECT agtype_to_int8(bool('false')); + agtype_to_int8 +---------------- + 0 +(1 row) + +-- should error +SELECT agtype_to_int2(bool('neither')); +ERROR: invalid input syntax for type boolean: "neither" +LINE 1: SELECT agtype_to_int2(bool('neither')); + ^ +SELECT agtype_to_int4(bool('neither')); +ERROR: invalid input syntax for type boolean: "neither" +LINE 1: SELECT agtype_to_int4(bool('neither')); + ^ +SELECT agtype_to_int8(bool('neither')); +ERROR: invalid input syntax for type boolean: "neither" +LINE 1: SELECT agtype_to_int8(bool('neither')); + ^ -- -- Cleanup -- diff --git a/regress/sql/agtype.sql b/regress/sql/agtype.sql index 5ecb36666..42bd2523b 100644 --- a/regress/sql/agtype.sql +++ b/regress/sql/agtype.sql @@ -554,16 +554,33 @@ SELECT bool_to_agtype(true) <> bool_to_agtype(false); -- SELECT agtype_to_int8(agtype_in('true')); SELECT agtype_to_int8(agtype_in('false')); +-- should return SQL NULL +SELECT agtype_to_int8(agtype_in('null')); +SELECT agtype_to_int8(NULL); +-- non agtype input +SELECT agtype_to_int8(1); +SELECT agtype_to_int8(3.14); +SELECT agtype_to_int8(3.14::numeric); +SELECT agtype_to_int8('3'); +SELECT agtype_to_int8(true); +SELECT agtype_to_int8(false); +SELECT agtype_to_int8('3.14'); +SELECT agtype_to_int8('true'); +SELECT agtype_to_int8('false'); +-- should fail +SELECT agtype_to_int8('neither'); +SELECT agtype_to_int8('NaN'); +SELECT agtype_to_int8('Inf'); +SELECT agtype_to_int8(NaN); +SELECT agtype_to_int8(Inf); -- -- Test boolean to integer cast -- SELECT agtype_to_int4(agtype_in('true')); SELECT agtype_to_int4(agtype_in('false')); -SELECT agtype_to_int4(agtype_in('null')); - -- --- Test agtype to integer cast +-- Test agtype to integer4 cast -- SELECT agtype_to_int4(agtype_in('1')); SELECT agtype_to_int4(agtype_in('1.45')); @@ -572,6 +589,60 @@ SELECT agtype_to_int4(agtype_in('1.444::numeric')); SELECT agtype_to_int4(agtype_in('"string"')); SELECT agtype_to_int4(agtype_in('[1, 2, 3]')); SELECT agtype_to_int4(agtype_in('{"int":1}')); +-- should return SQL NULL +SELECT agtype_to_int4(agtype_in('null')); +SELECT agtype_to_int4(NULL); +-- non agtype input +SELECT agtype_to_int4(1); +SELECT agtype_to_int4(3.14); +SELECT agtype_to_int4(3.14::numeric); +SELECT agtype_to_int4('3'); +SELECT agtype_to_int4(true); +SELECT agtype_to_int4(false); +SELECT agtype_to_int4('3.14'); +SELECT agtype_to_int4('true'); +SELECT agtype_to_int4('false'); +-- should error +SELECT agtype_to_int4('neither'); +SELECT agtype_to_int4('NaN'); +SELECT agtype_to_int4('Inf'); +SELECT agtype_to_int4(NaN); +SELECT agtype_to_int4(Inf); + +-- +-- Test boolean to integer2 cast +-- +SELECT agtype_to_int2(agtype_in('true')); +SELECT agtype_to_int2(agtype_in('false')); +-- +-- Test agtype to integer2 cast +-- +SELECT agtype_to_int2(agtype_in('1')); +SELECT agtype_to_int2(agtype_in('1.45')); +SELECT agtype_to_int2(agtype_in('1.444::numeric')); +-- These should all fail +SELECT agtype_to_int2(agtype_in('"string"')); +SELECT agtype_to_int2(agtype_in('[1, 2, 3]')); +SELECT agtype_to_int2(agtype_in('{"int":1}')); +-- should return SQL NULL +SELECT agtype_to_int2(agtype_in('null')); +SELECT agtype_to_int2(NULL); +-- non agtype input +SELECT agtype_to_int2(1); +SELECT agtype_to_int2(3.14); +SELECT agtype_to_int2(3.14::numeric); +SELECT agtype_to_int2('3'); +SELECT agtype_to_int2(true); +SELECT agtype_to_int2(false); +SELECT agtype_to_int2('3.14'); +SELECT agtype_to_int2('true'); +SELECT agtype_to_int2('false'); +-- should error +SELECT agtype_to_int2('neither'); +SELECT agtype_to_int2('NaN'); +SELECT agtype_to_int2('Inf'); +SELECT agtype_to_int2(NaN); +SELECT agtype_to_int2(Inf); -- -- Test agtype to int[] @@ -579,6 +650,11 @@ SELECT agtype_to_int4(agtype_in('{"int":1}')); SELECT agtype_to_int4_array(agtype_in('[1,2,3]')); SELECT agtype_to_int4_array(agtype_in('[1.6,2.3,3.66]')); SELECT agtype_to_int4_array(agtype_in('["6","7",3.66]')); +-- should error +SELECT agtype_to_int4_array(bool('true')); +SELECT agtype_to_int4_array((1,2,3,4,5)); +-- should return SQL NULL +SELECT agtype_to_int4_array(NULL); -- -- Map Literal diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 1080e0f89..dbaef17cc 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -582,7 +582,6 @@ SELECT * FROM cypher('type_coercion', $$ RETURN true $$) AS (i int); ---Invalid String Format SELECT * FROM cypher('type_coercion', $$ RETURN '1.0' $$) AS (i bigint); @@ -3034,6 +3033,20 @@ SELECT * FROM agtype('{"a": 1, "b": 2}'::agtype -> 'a'::text); -- Text BoolExpr expression node types SELECT * FROM bool(true AND false); +-- Issue 1329 +-- returns 1 +SELECT agtype_to_int2(bool('true')); +SELECT agtype_to_int4(bool('true')); +SELECT agtype_to_int8(bool('true')); +-- returns 0 +SELECT agtype_to_int2(bool('false')); +SELECT agtype_to_int4(bool('false')); +SELECT agtype_to_int8(bool('false')); +-- should error +SELECT agtype_to_int2(bool('neither')); +SELECT agtype_to_int4(bool('neither')); +SELECT agtype_to_int8(bool('neither')); + -- -- Cleanup -- diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index a2a4c30d1..947d39ce9 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -97,6 +97,7 @@ typedef enum /* type categories for datum_to_agtype */ } agt_type_category; static inline Datum agtype_from_cstring(char *str, int len); +static inline agtype_value *agtype_value_from_cstring(char *str, int len); size_t check_string_length(size_t len); static void agtype_in_agtype_annotation(void *pstate, char *annotation); static void agtype_in_object_start(void *pstate); @@ -344,13 +345,14 @@ Datum agtype_out(PG_FUNCTION_ARGS) } /* - * agtype_from_cstring + * agtype_value_from_cstring * - * Turns agtype string into an agtype Datum. + * Helper function to turn an agtype string into an agtype_value. * * Uses the agtype parser (with hooks) to construct an agtype. */ -static inline Datum agtype_from_cstring(char *str, int len) + +static inline agtype_value *agtype_value_from_cstring(char *str, int len) { agtype_lex_context *lex; agtype_in_state state; @@ -374,7 +376,21 @@ static inline Datum agtype_from_cstring(char *str, int len) parse_agtype(lex, &sem); /* after parsing, the item member has the composed agtype structure */ - PG_RETURN_POINTER(agtype_value_to_agtype(state.res)); + return state.res; +} + +/* + * agtype_from_cstring + * + * Turns agtype string into a Datum of agtype. + * + * Calls helper function + */ +static inline Datum agtype_from_cstring(char *str, int len) +{ + agtype_value *agtv = agtype_value_from_cstring(str, len); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv)); } size_t check_string_length(size_t len) @@ -2575,17 +2591,20 @@ PG_FUNCTION_INFO_V1(agtype_to_int8); */ Datum agtype_to_int8(PG_FUNCTION_ARGS) { - agtype *agtype_in = AG_GET_ARG_AGTYPE_P(0); agtype_value agtv; + agtype_value *agtv_p = NULL; + agtype_value *container = NULL; int64 result = 0x0; - agtype *arg_agt; + agtype *arg_agt = NULL; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); /* Return null if arg_agt is null. This covers SQL and Agtype NULLS */ if (arg_agt == NULL) + { PG_RETURN_NULL(); + } if (!agtype_extract_scalar(&arg_agt->root, &agtv) || (agtv.type != AGTV_FLOAT && @@ -2593,26 +2612,78 @@ Datum agtype_to_int8(PG_FUNCTION_ARGS) agtv.type != AGTV_NUMERIC && agtv.type != AGTV_STRING && agtv.type != AGTV_BOOL)) + { cannot_cast_agtype_value(agtv.type, "int"); + } - PG_FREE_IF_COPY(agtype_in, 0); + agtv_p = &agtv; + + /* + * If it is an agtype string, we need to convert the string component first. + * We need to do this because the string could be any type of value. Fx, + * integer, float, boolean, numeric, object, or array. Once converted, we + * need to remember scalar values are returned as a scalar array. We only + * care about scalar arrays. + */ + if (agtv_p->type == AGTV_STRING) + { + agtype_value *temp = NULL; + + /* convert the string to an agtype_value */ + temp = agtype_value_from_cstring(agtv_p->val.string.val, + agtv_p->val.string.len); + + if (temp->type != AGTV_ARRAY || + !temp->val.array.raw_scalar) + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* save the top agtype_value */ + container = temp; + /* get the wrapped agtype_value */ + temp = &temp->val.array.elems[0]; - if (agtv.type == AGTV_INTEGER) - result = agtv.val.int_value; - else if (agtv.type == AGTV_FLOAT) + if (temp->type == AGTV_FLOAT || + temp->type == AGTV_INTEGER || + temp->type == AGTV_NUMERIC || + temp->type == AGTV_BOOL) + { + agtv_p = temp; + } + } + + /* now check the rest */ + if (agtv_p->type == AGTV_INTEGER) + { + result = agtv_p->val.int_value; + } + else if (agtv_p->type == AGTV_FLOAT) + { result = DatumGetInt64(DirectFunctionCall1(dtoi8, - Float8GetDatum(agtv.val.float_value))); - else if (agtv.type == AGTV_NUMERIC) + Float8GetDatum(agtv_p->val.float_value))); + } + else if (agtv_p->type == AGTV_NUMERIC) + { result = DatumGetInt64(DirectFunctionCall1(numeric_int8, - NumericGetDatum(agtv.val.numeric))); - else if (agtv.type == AGTV_STRING) - result = DatumGetInt64(DirectFunctionCall1(int8in, - CStringGetDatum(agtv.val.string.val))); - else if(agtv.type == AGTV_BOOL) - result = DatumGetInt64(DirectFunctionCall1(bool_int4, - BoolGetDatum(agtv.val.boolean))); + NumericGetDatum(agtv_p->val.numeric))); + } + else if(agtv_p->type == AGTV_BOOL) + { + result = (agtv_p->val.boolean) ? 1 : 0; + } else - elog(ERROR, "invalid agtype type: %d", (int)agtv.type); + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* free the container, if it was used */ + if (container) + { + pfree(container); + } + + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_INT64(result); } @@ -2624,10 +2695,11 @@ PG_FUNCTION_INFO_V1(agtype_to_int4); */ Datum agtype_to_int4(PG_FUNCTION_ARGS) { - agtype *agtype_in = AG_GET_ARG_AGTYPE_P(0); agtype_value agtv; + agtype_value *agtv_p = NULL; + agtype_value *container = NULL; int32 result = 0x0; - agtype *arg_agt; + agtype *arg_agt = NULL; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -2648,38 +2720,81 @@ Datum agtype_to_int4(PG_FUNCTION_ARGS) cannot_cast_agtype_value(agtv.type, "int"); } - PG_FREE_IF_COPY(agtype_in, 0); + agtv_p = &agtv; - if (agtv.type == AGTV_INTEGER) - { + /* + * If it is an agtype string, we need to convert the string component first. + * We need to do this because the string could be any type of value. Fx, + * integer, float, boolean, numeric, object, or array. Once converted, we + * need to remember scalar values are returned as a scalar array. We only + * care about scalar arrays. + */ + if (agtv_p->type == AGTV_STRING) + { + agtype_value *temp = NULL; + + /* convert the string to an agtype_value */ + temp = agtype_value_from_cstring(agtv_p->val.string.val, + agtv_p->val.string.len); + + if (temp->type != AGTV_ARRAY || + !temp->val.array.raw_scalar) + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* save the top agtype_value */ + container = temp; + /* get the wrapped agtype_value */ + temp = &temp->val.array.elems[0]; + + if (temp->type == AGTV_FLOAT || + temp->type == AGTV_INTEGER || + temp->type == AGTV_NUMERIC || + temp->type == AGTV_BOOL) + { + agtv_p = temp; + } + } + + /* now check the rest */ + if (agtv_p->type == AGTV_INTEGER) + { result = DatumGetInt32(DirectFunctionCall1(int84, - Int64GetDatum(agtv.val.int_value))); + Int64GetDatum(agtv_p->val.int_value))); } - else if (agtv.type == AGTV_FLOAT) - { + else if (agtv_p->type == AGTV_FLOAT) + { result = DatumGetInt32(DirectFunctionCall1(dtoi4, - Float8GetDatum(agtv.val.float_value))); + Float8GetDatum(agtv_p->val.float_value))); } - else if (agtv.type == AGTV_NUMERIC) - { + else if (agtv_p->type == AGTV_NUMERIC) + { result = DatumGetInt32(DirectFunctionCall1(numeric_int4, - NumericGetDatum(agtv.val.numeric))); + NumericGetDatum(agtv_p->val.numeric))); } - else if (agtv.type == AGTV_STRING) - { + else if (agtv_p->type == AGTV_STRING) + { result = DatumGetInt32(DirectFunctionCall1(int4in, - CStringGetDatum(agtv.val.string.val))); + CStringGetDatum(agtv_p->val.string.val))); } - else if (agtv.type == AGTV_BOOL) - { - result = DatumGetInt64(DirectFunctionCall1(bool_int4, - BoolGetDatum(agtv.val.boolean))); + else if (agtv_p->type == AGTV_BOOL) + { + result = (agtv_p->val.boolean) ? 1 : 0; } else - { - elog(ERROR, "invalid agtype type: %d", (int)agtv.type); + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* free the container, if it was used */ + if (container) + { + pfree(container); } + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_INT32(result); } @@ -2690,41 +2805,105 @@ PG_FUNCTION_INFO_V1(agtype_to_int2); */ Datum agtype_to_int2(PG_FUNCTION_ARGS) { - agtype *agtype_in = AG_GET_ARG_AGTYPE_P(0); agtype_value agtv; + agtype_value *agtv_p = NULL; + agtype_value *container = NULL; int16 result = 0x0; - agtype *arg_agt; + agtype *arg_agt = NULL; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); /* Return null if arg_agt is null. This covers SQL and Agtype NULLS */ if (arg_agt == NULL) + { PG_RETURN_NULL(); + } if (!agtype_extract_scalar(&arg_agt->root, &agtv) || (agtv.type != AGTV_FLOAT && agtv.type != AGTV_INTEGER && agtv.type != AGTV_NUMERIC && - agtv.type != AGTV_STRING)) + agtv.type != AGTV_STRING && + agtv.type != AGTV_BOOL)) + { cannot_cast_agtype_value(agtv.type, "int"); + } - PG_FREE_IF_COPY(agtype_in, 0); + agtv_p = &agtv; + + /* + * If it is an agtype string, we need to convert the string component first. + * We need to do this because the string could be any type of value. Fx, + * integer, float, boolean, numeric, object, or array. Once converted, we + * need to remember scalar values are returned as a scalar array. We only + * care about scalar arrays. + */ + if (agtv_p->type == AGTV_STRING) + { + agtype_value *temp = NULL; - if (agtv.type == AGTV_INTEGER) + /* convert the string to an agtype_value */ + temp = agtype_value_from_cstring(agtv_p->val.string.val, + agtv_p->val.string.len); + + if (temp->type != AGTV_ARRAY || + !temp->val.array.raw_scalar) + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* save the top agtype_value */ + container = temp; + /* get the wrapped agtype_value */ + temp = &temp->val.array.elems[0]; + + if (temp->type == AGTV_FLOAT || + temp->type == AGTV_INTEGER || + temp->type == AGTV_NUMERIC || + temp->type == AGTV_BOOL) + { + agtv_p = temp; + } + } + + /* now check the rest */ + if (agtv_p->type == AGTV_INTEGER) + { result = DatumGetInt16(DirectFunctionCall1(int82, - Int64GetDatum(agtv.val.int_value))); - else if (agtv.type == AGTV_FLOAT) - result = DatumGetInt32(DirectFunctionCall1(dtoi2, - Float8GetDatum(agtv.val.float_value))); - else if (agtv.type == AGTV_NUMERIC) + Int64GetDatum(agtv_p->val.int_value))); + } + else if (agtv_p->type == AGTV_FLOAT) + { + result = DatumGetInt16(DirectFunctionCall1(dtoi2, + Float8GetDatum(agtv_p->val.float_value))); + } + else if (agtv_p->type == AGTV_NUMERIC) + { result = DatumGetInt16(DirectFunctionCall1(numeric_int2, - NumericGetDatum(agtv.val.numeric))); - else if (agtv.type == AGTV_STRING) + NumericGetDatum(agtv_p->val.numeric))); + } + else if (agtv_p->type == AGTV_STRING) + { result = DatumGetInt16(DirectFunctionCall1(int2in, - CStringGetDatum(agtv.val.string.val))); + CStringGetDatum(agtv_p->val.string.val))); + } + else if (agtv_p->type == AGTV_BOOL) + { + result = (agtv_p->val.boolean) ? 1 : 0; + } else - elog(ERROR, "invalid agtype type: %d", (int)agtv.type); + { + elog(ERROR, "invalid agtype type: %d", (int)agtv_p->type); + } + + /* free the container, if it was used */ + if (container) + { + pfree(container); + } + + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_INT16(result); } @@ -2862,18 +3041,41 @@ PG_FUNCTION_INFO_V1(agtype_to_int4_array); /* * Cast agtype to int4[]. + * + * TODO: + * + * We either need to change the function definition in age--x.x.x.sql + * to something like agtype[] or we need to make this function work + * for "any" type input. Right now it only works for an agtype array but + * it takes "any" input. Hence the additional code added to block anything + * other than agtype. */ Datum agtype_to_int4_array(PG_FUNCTION_ARGS) { - agtype *agtype_in = AG_GET_ARG_AGTYPE_P(0); + agtype_iterator *agtype_iterator = NULL; + agtype *agtype_in = NULL; agtype_value agtv; agtype_iterator_token agtv_token; Datum *array_value; ArrayType *result; + Oid arg_type = InvalidOid; int element_size; int i; - agtype_iterator *agtype_iterator = agtype_iterator_init(&agtype_in->root); + /* get the input data type */ + arg_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + + /* verify the input is agtype */ + if (arg_type != AGTYPEOID) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument must resolve to agtype"))); + } + + agtype_in = AG_GET_ARG_AGTYPE_P(0); + + agtype_iterator = agtype_iterator_init(&agtype_in->root); agtv_token = agtype_iterator_next(&agtype_iterator, &agtv, false); if (agtv.type != AGTV_ARRAY) @@ -9261,8 +9463,8 @@ agtype_value *alter_properties(agtype_value *original_properties, * extract_variadic_args. */ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, - int variadic_offset, - int expected_nargs) + int variadic_offset, + int expected_nargs) { int nargs; Datum *args = NULL;