This is an automated email from the ASF dual-hosted git repository.

jgemignani pushed a commit to branch PG15
in repository https://gitbox.apache.org/repos/asf/age.git

commit d4ad7bfaa95e10cb50b08707acc4bb44819876e1
Author: John Gemignani <[email protected]>
AuthorDate: Wed Nov 1 16:27:37 2023 -0700

    Fix Issue 1329 - agtype_to_int4 crash (#1339)
    
    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.
---
 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 | 308 ++++++++++++++++++++++++++++++++-------
 5 files changed, 721 insertions(+), 67 deletions(-)

diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out
index f8860b63..53217f67 100644
--- a/regress/expected/agtype.out
+++ b/regress/expected/agtype.out
@@ -2744,6 +2744,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
 --
@@ -2759,14 +2844,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 
@@ -2788,11 +2867,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 type 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[]
 --
@@ -2814,6 +3110,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 3edb7dd6..d7257228 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -1336,11 +1336,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 type bigint: "1.0"
+ i 
+---
+ 1
+(1 row)
+
 -- Casting to ints that will cause overflow
 SELECT * FROM cypher('type_coercion', $$
        RETURN 10000000000000000000
@@ -7559,6 +7562,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 718e6234..c17290af 100644
--- a/regress/sql/agtype.sql
+++ b/regress/sql/agtype.sql
@@ -674,16 +674,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'));
@@ -692,6 +709,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[]
@@ -699,6 +770,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 931159b6..0aa62a0b 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -625,7 +625,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);
@@ -3078,6 +3077,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 41235106..2914b52c 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -104,6 +104,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);
@@ -361,13 +362,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;
@@ -391,7 +393,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)
@@ -2609,17 +2625,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 &&
@@ -2627,26 +2646,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 (temp->type == AGTV_FLOAT ||
+            temp->type == AGTV_INTEGER ||
+            temp->type == AGTV_NUMERIC ||
+            temp->type == AGTV_BOOL)
+        {
+            agtv_p = temp;
+        }
+    }
 
-    if (agtv.type == AGTV_INTEGER)
-        result = agtv.val.int_value;
-    else if (agtv.type == AGTV_FLOAT)
+    /* 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);
 }
@@ -2658,10 +2729,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);
@@ -2682,38 +2754,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 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;
+        }
+    }
 
-    if (agtv.type == AGTV_INTEGER)
+    /* 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)
+    else if (agtv_p->type == AGTV_BOOL)
     {
-        result = DatumGetInt64(DirectFunctionCall1(bool_int4,
-                    BoolGetDatum(agtv.val.boolean)));
+        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);
 }
 
@@ -2724,41 +2839,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;
+
+        /* 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;
+        }
+    }
 
-    if (agtv.type == AGTV_INTEGER)
+    /* 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);
 }
@@ -2896,18 +3075,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)
@@ -9450,8 +9652,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;

Reply via email to