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

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


The following commit(s) were added to refs/heads/PG14 by this push:
     new 14197f09 PR to merge master branch into PG14 branch (#1135)
14197f09 is described below

commit 14197f098d8f5c8462266e08ecbc07efc50feeba
Author: John Gemignani <[email protected]>
AuthorDate: Mon Aug 14 15:27:24 2023 -0700

    PR to merge master branch into PG14 branch (#1135)
    
    PR for moving the PG14 branch up to the current master branch.
    
    Contains all of the latest work in the master branch, which is currently
    at PostgreSQL version 14 at the time of this PR. When this PR is merged,
    PG14 will be the latest and represent PostgreSQL version 14 as the master
    moves to PostgreSQL version 15.
---
 RELEASE                             |   5 ++
 age--1.3.0.sql                      |  17 +++-
 docker/Dockerfile                   |  26 ++++--
 docker/Dockerfile.dev               |  12 ++-
 regress/expected/expr.out           | 133 +++++++++++++++++++++++++++
 regress/sql/expr.sql                |  45 ++++++++++
 src/backend/executor/cypher_utils.c |   3 +-
 src/backend/parser/cypher_analyze.c |  38 ++++++++
 src/backend/parser/cypher_clause.c  |  37 +++++++-
 src/backend/utils/adt/agtype.c      | 175 ++++++++++++++++++++++++++++++++----
 10 files changed, 458 insertions(+), 33 deletions(-)

diff --git a/RELEASE b/RELEASE
index a49451ca..329a9307 100644
--- a/RELEASE
+++ b/RELEASE
@@ -21,3 +21,8 @@ Apache AGE 0.0.0 - Release Notes
 
 NOTE: This is an initial release of PG14. There are no upgrade
       scripts to this release.
+
+**************************************************************
+***** THIS IS THE MASTER BRANCH TEMPLATE FOR THE RELEASE *****
+***** FILE. MODIFY AS NECESSARY FOR THE SPECIFIC RELEASE *****
+**************************************************************
diff --git a/age--1.3.0.sql b/age--1.3.0.sql
index 8bfa9a93..90097a7e 100644
--- a/age--1.3.0.sql
+++ b/age--1.3.0.sql
@@ -365,7 +365,7 @@ AS 'MODULE_PATHNAME';
 CREATE FUNCTION ag_catalog._label_name(graph_oid oid, graphid)
 RETURNS cstring
 LANGUAGE c
-STABLE
+IMMUTABLE
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
@@ -3104,7 +3104,7 @@ AS 'MODULE_PATHNAME';
 CREATE FUNCTION ag_catalog._agtype_build_vertex(graphid, cstring, agtype)
 RETURNS agtype
 LANGUAGE c
-STABLE
+IMMUTABLE
 CALLED ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
@@ -3112,10 +3112,11 @@ AS 'MODULE_PATHNAME';
 --
 -- agtype - edge
 --
-CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid, 
cstring, agtype)
+CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
+                                              cstring, agtype)
 RETURNS agtype
 LANGUAGE c
-STABLE
+IMMUTABLE
 CALLED ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
@@ -3546,6 +3547,14 @@ RETURNS NULL ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
+CREATE FUNCTION ag_catalog.age_tostringlist(variadic "any")
+RETURNS agtype
+LANGUAGE c
+IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
 CREATE FUNCTION ag_catalog.age_size(variadic "any")
 RETURNS agtype
 LANGUAGE c
diff --git a/docker/Dockerfile b/docker/Dockerfile
index c30d6931..ac9803d5 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -18,15 +18,27 @@
 
 FROM postgres:14
 
-RUN apt-get update
-RUN apt-get install --assume-yes --no-install-recommends --no-install-suggests 
\
-  bison \
-  build-essential \
-  flex \
-  postgresql-server-dev-14
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends --no-install-suggests \
+       bison \
+       build-essential \
+       flex \
+       postgresql-server-dev-14 \
+       locales
+
+ENV LANG=en_US.UTF-8
+ENV LC_COLLATE=en_US.UTF-8
+ENV LC_CTYPE=en_US.UTF-8
+
+RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
+    && locale-gen \
+    && update-locale LANG=en_US.UTF-8
 
 COPY . /age
-RUN cd /age && make install
+
+WORKDIR /age
+
+RUN make && make install
 
 COPY docker/docker-entrypoint-initdb.d/00-create-extension-age.sql 
/docker-entrypoint-initdb.d/00-create-extension-age.sql
 
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
index 5e47d49b..8162f5b5 100644
--- a/docker/Dockerfile.dev
+++ b/docker/Dockerfile.dev
@@ -17,15 +17,23 @@
 #
 
 
-FROM postgres:14-buster
+FROM postgres:14
 
 RUN apt-get update
 RUN apt-get install --assume-yes --no-install-recommends --no-install-suggests 
\
   bison \
   build-essential \
   flex \
-  postgresql-server-dev-14
+  postgresql-server-dev-14 \
+  locales
 
+ENV LANG=en_US.UTF-8
+ENV LC_COLLATE=en_US.UTF-8
+ENV LC_CTYPE=en_US.UTF-8
+
+RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \
+    && locale-gen \
+    && update-locale LANG=en_US.UTF-8
 COPY . /age
 
 # Set current working directory to /age/ and build.
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 94c8d180..1cdfaddb 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -3284,6 +3284,69 @@ ERROR:  function ag_catalog.age_tostring() does not exist
 LINE 1: SELECT * FROM cypher('expr', $$ RETURN toString() $$) AS (re...
                                                ^
 HINT:  No function matches the given name and argument types. You might need 
to add explicit type casts.
+-- toStringList() --
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([5, 10, 7.8, 9, 1.3]) 
+$$) AS (toStringList agtype);
+          tostringlist          
+--------------------------------
+ ["5", "10", "7.8", "9", "1.3"]
+(1 row)
+
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList(['test', 89, 'again', 7.1, 9]) 
+$$) AS (toStringList agtype);
+            tostringlist             
+-------------------------------------
+ ["test", "89", "again", "7.1", "9"]
+(1 row)
+
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([null, false, true, 'string']) 
+$$) AS (toStringList agtype);
+         tostringlist         
+------------------------------
+ [null, null, null, "string"]
+(1 row)
+
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([9.123456789, 5.123, 1.12345, 0.123123]) 
+$$) AS (toStringList agtype);
+                  tostringlist                   
+-------------------------------------------------
+ ["9.123456789", "5.123", "1.12345", "0.123123"]
+(1 row)
+
+-- should return null
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([null]) 
+$$) AS (toStringList agtype);
+ tostringlist 
+--------------
+ [null]
+(1 row)
+
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([true, false, true, true]) 
+$$) AS (toStringList agtype);
+       tostringlist       
+--------------------------
+ [null, null, null, null]
+(1 row)
+
+-- should fail
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([['a', b]]) 
+$$) AS (toStringList agtype);
+ERROR:  could not find rte for b
+LINE 2:     RETURN toStringList([['a', b]]) 
+                                       ^
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([test]) 
+$$) AS (toStringList agtype);
+ERROR:  could not find rte for test
+LINE 2:     RETURN toStringList([test]) 
+                                 ^
 --
 -- reverse(string)
 --
@@ -6913,9 +6976,79 @@ SELECT * FROM cypher('graph_395', $$ MATCH 
(p:Project)-[:Has]->(t:Task)-[:Assign
  {"pn": "Project B", "tasks": [{"tn": "Task C", "users": [{"id": 
1407374883553282, "label": "Person", "properties": {"age": 43, "name": 
"Bob"}}::vertex]}]}
 (2 rows)
 
+---
+--- Fix: Segmentation fault when using specific names for tables #1124
+---
+--- The following are just a few commands to test SQLValueFunction types
+---
+SELECT count(*) FROM CURRENT_ROLE;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM CURRENT_USER;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM USER;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM SESSION_USER;
+ count 
+-------
+     1
+(1 row)
+
+SELECT * FROM CURRENT_CATALOG;
+  current_catalog   
+--------------------
+ contrib_regression
+(1 row)
+
+SELECT * FROM CURRENT_SCHEMA;
+ current_schema 
+----------------
+ ag_catalog
+(1 row)
+
+SELECT * FROM create_graph('issue_1124');
+NOTICE:  graph "issue_1124" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+SELECT results, pg_typeof(user) FROM cypher('issue_1124', $$ CREATE (u) RETURN 
u $$) AS (results agtype), user;
+                            results                             | pg_typeof 
+----------------------------------------------------------------+-----------
+ {"id": 281474976710657, "label": "", "properties": {}}::vertex | name
+(1 row)
+
+SELECT results, pg_typeof(user) FROM cypher('issue_1124', $$ MATCH (u) RETURN 
u $$) AS (results agtype), user;
+                            results                             | pg_typeof 
+----------------------------------------------------------------+-----------
+ {"id": 281474976710657, "label": "", "properties": {}}::vertex | name
+(1 row)
+
 --
 -- Cleanup
 --
+SELECT * FROM drop_graph('issue_1124', true);
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table issue_1124._ag_label_vertex
+drop cascades to table issue_1124._ag_label_edge
+NOTICE:  graph "issue_1124" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
 SELECT * FROM drop_graph('graph_395', true);
 NOTICE:  drop cascades to 7 other objects
 DETAIL:  drop cascades to table graph_395._ag_label_vertex
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index d4a7efbd..82421d9d 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -1441,6 +1441,33 @@ SELECT * FROM age_toString(agtype_in(null));
 -- should fail
 SELECT * FROM age_toString();
 SELECT * FROM cypher('expr', $$ RETURN toString() $$) AS (results agtype);
+-- toStringList() --
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([5, 10, 7.8, 9, 1.3]) 
+$$) AS (toStringList agtype);
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList(['test', 89, 'again', 7.1, 9]) 
+$$) AS (toStringList agtype);
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([null, false, true, 'string']) 
+$$) AS (toStringList agtype);
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([9.123456789, 5.123, 1.12345, 0.123123]) 
+$$) AS (toStringList agtype);
+-- should return null
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([null]) 
+$$) AS (toStringList agtype);
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([true, false, true, true]) 
+$$) AS (toStringList agtype);
+-- should fail
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([['a', b]]) 
+$$) AS (toStringList agtype);
+SELECT * FROM cypher('expr', $$ 
+    RETURN toStringList([test]) 
+$$) AS (toStringList agtype);
 
 --
 -- reverse(string)
@@ -2810,9 +2837,27 @@ SELECT * FROM cypher('graph_395', $$ MATCH 
(p:Project)-[:Has]->(t:Task)-[:Assign
                                      WITH p, collect(task) AS tasks
                                      WITH {pn: p.name, tasks:tasks} AS project
                                      RETURN project $$) AS (p agtype);
+
+---
+--- Fix: Segmentation fault when using specific names for tables #1124
+---
+--- The following are just a few commands to test SQLValueFunction types
+---
+
+SELECT count(*) FROM CURRENT_ROLE;
+SELECT count(*) FROM CURRENT_USER;
+SELECT count(*) FROM USER;
+SELECT count(*) FROM SESSION_USER;
+SELECT * FROM CURRENT_CATALOG;
+SELECT * FROM CURRENT_SCHEMA;
+SELECT * FROM create_graph('issue_1124');
+SELECT results, pg_typeof(user) FROM cypher('issue_1124', $$ CREATE (u) RETURN 
u $$) AS (results agtype), user;
+SELECT results, pg_typeof(user) FROM cypher('issue_1124', $$ MATCH (u) RETURN 
u $$) AS (results agtype), user;
+
 --
 -- Cleanup
 --
+SELECT * FROM drop_graph('issue_1124', true);
 SELECT * FROM drop_graph('graph_395', true);
 SELECT * FROM drop_graph('chained', true);
 SELECT * FROM drop_graph('VLE', true);
diff --git a/src/backend/executor/cypher_utils.c 
b/src/backend/executor/cypher_utils.c
index 16eb0888..0bf3430f 100644
--- a/src/backend/executor/cypher_utils.c
+++ b/src/backend/executor/cypher_utils.c
@@ -254,7 +254,8 @@ HeapTuple insert_entity_tuple_cid(ResultRelInfo 
*resultRelInfo,
     // Insert index entries for the tuple
     if (resultRelInfo->ri_NumIndices > 0)
     {
-        ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false, 
false, NULL, NIL);
+        ExecInsertIndexTuples(resultRelInfo, elemTupleSlot, estate, false,
+                              false, NULL, NIL);
     }
 
     return tuple;
diff --git a/src/backend/parser/cypher_analyze.c 
b/src/backend/parser/cypher_analyze.c
index f1c481c8..cafe880d 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -167,6 +167,21 @@ static bool convert_cypher_walker(Node *node, ParseState 
*pstate)
                      parser_errposition(pstate, exprLocation((Node 
*)funcexpr))));
         }
 
+        /*
+         * From PG -
+         * SQLValueFunction - parameterless functions with special grammar
+         *                    productions.
+         *
+         * These are a special case that needs to be ignored.
+         *
+         * TODO: This likely needs to be done with XmlExpr types, and maybe
+         *       a few others too.
+         */
+        if (IsA(funcexpr, SQLValueFunction))
+        {
+            return false;
+        }
+
         return expression_tree_walker((Node *)funcexpr->args,
                                       convert_cypher_walker, pstate);
     }
@@ -296,6 +311,21 @@ static bool is_rte_cypher(RangeTblEntry *rte)
  */
 static bool is_func_cypher(FuncExpr *funcexpr)
 {
+    /*
+     * From PG -
+     * SQLValueFunction - parameterless functions with special grammar
+     *                    productions.
+     *
+     * These are a special case that needs to be ignored.
+     *
+     *  TODO: This likely needs to be done with XmlExpr types, and maybe
+     *        a few others too.
+     */
+    if (IsA(funcexpr, SQLValueFunction))
+    {
+        return false;
+    }
+
     return is_oid_ag_func(funcexpr->funcid, "cypher");
 }
 
@@ -756,8 +786,16 @@ static Query *analyze_cypher_and_coerce(List *stmt, 
RangeTblFunction *rtfunc,
 
     pnsi = addRangeTableEntryForSubquery(pstate, subquery, makeAlias("_", NIL),
                                         lateral, true);
+
     rtindex = list_length(pstate->p_rtable);
     Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate
+    if (rtindex !=1 )
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_DATATYPE_MISMATCH),
+                 errmsg("invalid value for rtindex")));
+    }
+
 
     addNSItemToQuery(pstate, pnsi, true, true, true);
     query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1);
diff --git a/src/backend/parser/cypher_clause.c 
b/src/backend/parser/cypher_clause.c
index 59af484f..299f1203 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -1334,6 +1334,13 @@ static Query *transform_cypher_unwind(cypher_parsestate 
*cpstate,
         pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true);
         rtindex = list_length(pstate->p_rtable);
         Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
+        if (rtindex != 1)
+        {
+            ereport(ERROR,
+                    (errcode(ERRCODE_DATATYPE_MISMATCH),
+                     errmsg("invalid value for rtindex")));
+        }
+
         query->targetList = expandNSItemAttrs(pstate, pnsi, 0, -1);
     }
 
@@ -1344,10 +1351,12 @@ static Query *transform_cypher_unwind(cypher_parsestate 
*cpstate,
         ereport(ERROR,
                 (errcode(ERRCODE_DUPLICATE_ALIAS),
                         errmsg("duplicate variable \"%s\"", 
self->target->name),
-                        parser_errposition((ParseState *) cpstate, 
target_syntax_loc)));
+                        parser_errposition((ParseState *) cpstate,
+                                           target_syntax_loc)));
     }
 
-    expr = transform_cypher_expr(cpstate, self->target->val, 
EXPR_KIND_SELECT_TARGET);
+    expr = transform_cypher_expr(cpstate, self->target->val,
+                                 EXPR_KIND_SELECT_TARGET);
 
     unwind = makeFuncCall(list_make1(makeString("age_unnest")), NIL,
                           COERCE_SQL_SYNTAX, -1);
@@ -2309,6 +2318,12 @@ static Query 
*transform_cypher_clause_with_where(cypher_parsestate *cpstate,
         Assert(pnsi != NULL);
         rtindex = list_length(pstate->p_rtable);
         Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate
+        if (rtindex != 1)
+        {
+            ereport(ERROR,
+                    (errcode(ERRCODE_DATATYPE_MISMATCH),
+                     errmsg("invalid value for rtindex")));
+        }
 
         /*
          * add all the target entries in pnsi to the current target list to 
pass
@@ -2583,6 +2598,12 @@ static Query 
*transform_cypher_match_pattern(cypher_parsestate *cpstate,
             rte = pnsi->p_rte;
             rtindex = list_length(pstate->p_rtable);
             Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
+            if (rtindex != 1)
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_DATATYPE_MISMATCH),
+                         errmsg("invalid value for rtindex")));
+            }
 
             /*
              * add all the target entries in rte to the current target list to 
pass
@@ -3169,7 +3190,8 @@ static List 
*make_join_condition_for_edge(cypher_parsestate *cpstate,
             args = list_make3(left_id, right_id, entity->expr);
 
             // add to quals
-            quals = lappend(quals, makeFuncCall(qualified_func_name, args, 
COERCE_EXPLICIT_CALL, -1));
+            quals = lappend(quals, makeFuncCall(qualified_func_name, args,
+                                                COERCE_EXPLICIT_CALL, -1));
         }
 
         /*
@@ -4358,7 +4380,8 @@ static Node *make_qual(cypher_parsestate *cpstate,
 
 
         args = list_make1(entity->expr);
-        node = (Node *)makeFuncCall(qualified_name, args, 
COERCE_EXPLICIT_CALL, -1);
+        node = (Node *)makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL,
+                                    -1);
     }
     else
     {
@@ -6771,6 +6794,12 @@ static void handle_prev_clause(cypher_parsestate 
*cpstate, Query *query,
     if (first_rte)
     {
         Assert(rtindex == 1);
+        if (rtindex != 1)
+        {
+            ereport(ERROR,
+                    (errcode(ERRCODE_DATATYPE_MISMATCH),
+                     errmsg("invalid value for rtindex")));
+        }
     }
 
     // add all the rte's attributes to the current queries targetlist
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index 376137ab..815a731a 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -31,6 +31,7 @@
 #include "postgres.h"
 
 #include <math.h>
+#include <float.h>
 
 #include "access/genam.h"
 #include "access/heapam.h"
@@ -182,7 +183,7 @@ static int extract_variadic_args_min(FunctionCallInfo 
fcinfo,
                                      int variadic_start, bool convert_unknown,
                                      Datum **args, Oid **types, bool **nulls,
                                      int min_num_args);
-static agtype_value* agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo);
+static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo);
 agtype_value *agtype_composite_to_agtype_value_binary(agtype *a);
 
 /* global storage of  OID for agtype and _agtype */
@@ -2364,7 +2365,7 @@ Datum make_edge(Datum id, Datum startid, Datum endid, 
Datum label,
                                properties);
 }
 
-static agtype_value* agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
+static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
 {
     int nargs;
     int i;
@@ -2377,7 +2378,9 @@ static agtype_value* 
agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
     nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
 
     if (nargs < 0)
-        PG_RETURN_NULL();
+    {
+        return NULL;
+    }
 
     if (nargs % 2 != 0)
     {
@@ -2420,7 +2423,14 @@ PG_FUNCTION_INFO_V1(agtype_build_map);
  */
 Datum agtype_build_map(PG_FUNCTION_ARGS)
 {
-    agtype_value *result= agtype_build_map_as_agtype_value(fcinfo);
+    agtype_value *result = NULL;
+
+    result = agtype_build_map_as_agtype_value(fcinfo);
+    if (result == NULL)
+    {
+        PG_RETURN_NULL();
+    }
+
     PG_RETURN_POINTER(agtype_value_to_agtype(result));
 }
 
@@ -2448,8 +2458,16 @@ PG_FUNCTION_INFO_V1(agtype_build_map_nonull);
  */
 Datum agtype_build_map_nonull(PG_FUNCTION_ARGS)
 {
-    agtype_value *result= agtype_build_map_as_agtype_value(fcinfo);
+    agtype_value *result = NULL;
+
+    result = agtype_build_map_as_agtype_value(fcinfo);
+    if (result == NULL)
+    {
+        PG_RETURN_NULL();
+    }
+
     remove_null_from_agtype_object(result);
+
     PG_RETURN_POINTER(agtype_value_to_agtype(result));
 }
 
@@ -5446,18 +5464,26 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
     if (type != AGTYPEOID)
     {
         if (type == INT2OID)
+        {
             result = (int64) DatumGetInt16(arg);
+        }
         else if (type == INT4OID)
+        {
             result = (int64) DatumGetInt32(arg);
+        }
         else if (type == INT8OID)
+        {
             result = (int64) DatumGetInt64(arg);
+        }
         else if (type == FLOAT4OID)
         {
             float4 f = DatumGetFloat4(arg);
 
             if (isnan(f) || isinf(f) ||
-                f < PG_INT64_MIN || f > PG_INT64_MAX)
+                f < (float4)PG_INT64_MIN || f > (float4)PG_INT64_MAX)
+            {
                 PG_RETURN_NULL();
+            }
 
             result = (int64) f;
         }
@@ -5466,8 +5492,10 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
             float8 f = DatumGetFloat8(arg);
 
             if (isnan(f) || isinf(f) ||
-                f < PG_INT64_MIN || f > PG_INT64_MAX)
+                f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+            {
                 PG_RETURN_NULL();
+            }
 
             result = (int64) f;
         }
@@ -5479,17 +5507,23 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
                 numeric_float8_no_overflow, arg));
 
             if (isnan(f) || isinf(f) ||
-                f < PG_INT64_MIN || f > PG_INT64_MAX)
+                f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+            {
                 PG_RETURN_NULL();
+            }
 
             result = (int64) f;
         }
         else if (type == CSTRINGOID || type == TEXTOID)
         {
             if (type == CSTRINGOID)
+            {
                 string = DatumGetCString(arg);
+            }
             else
+            {
                 string = text_to_cstring(DatumGetTextPP(arg));
+            }
 
             /* convert it if it is a regular integer string */
             is_valid = scanint8(string, true, &result);
@@ -5499,7 +5533,7 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
              */
             if (!is_valid)
             {
-                float f;
+                float8 f;
 
                 f = float8in_internal_null(string, NULL, "double precision",
                                            string, &is_valid);
@@ -5508,16 +5542,20 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
                  * return null.
                  */
                 if (!is_valid || isnan(f) || isinf(f) ||
-                    f < PG_INT64_MIN || f > PG_INT64_MAX)
+                    f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+                {
                     PG_RETURN_NULL();
+                }
 
                 result = (int64) f;
             }
         }
         else
+        {
             ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                             errmsg("toInteger() unsupported argument type %d",
                                    type)));
+        }
     }
     else
     {
@@ -5537,11 +5575,13 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
             result = agtv_value->val.int_value;
         else if (agtv_value->type == AGTV_FLOAT)
         {
-            float f = agtv_value->val.float_value;
+            float8 f = agtv_value->val.float_value;
 
             if (isnan(f) || isinf(f) ||
-                f < PG_INT64_MIN || f > PG_INT64_MAX)
+                f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+            {
                 PG_RETURN_NULL();
+            }
 
             result = (int64) f;
         }
@@ -5554,8 +5594,10 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
                 numeric_float8_no_overflow, num));
 
             if (isnan(f) || isinf(f) ||
-                f < PG_INT64_MIN || f > PG_INT64_MAX)
+                f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+            {
                 PG_RETURN_NULL();
+            }
 
             result = (int64) f;
         }
@@ -5572,7 +5614,7 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
              */
             if (!is_valid)
             {
-                float f;
+                float8 f;
 
                 f = float8in_internal_null(string, NULL, "double precision",
                                            string, &is_valid);
@@ -5582,18 +5624,24 @@ Datum age_tointeger(PG_FUNCTION_ARGS)
                  * return null.
                  */
                 if (!is_valid || isnan(f) || isinf(f) ||
-                    f < PG_INT64_MIN || f > PG_INT64_MAX)
+                    f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX)
+                {
                     PG_RETURN_NULL();
+                }
 
                 result = (int64) f;
             }
             else
+            {
                 free(string);
+            }
         }
         else
+        {
             ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                             errmsg("toInteger() unsupported argument agtype 
%d",
                                    agtv_value->type)));
+        }
     }
 
     /* build the result */
@@ -6149,6 +6197,103 @@ Datum age_tostring(PG_FUNCTION_ARGS)
     PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result));
 }
 
+PG_FUNCTION_INFO_V1(age_tostringlist);
+/*
+ * toStringList() converts a list of values and returns a list of String 
values. 
+ * If any values are not convertible to string point they will be null in the 
list returned.
+ */
+Datum age_tostringlist(PG_FUNCTION_ARGS)
+{
+    agtype *agt_arg = NULL;
+    agtype_in_state agis_result;
+    agtype_value *elem;
+    agtype_value string_elem;
+    int count;
+    int i;
+    char buffer[64];
+
+    /* check for null */
+    if (PG_ARGISNULL(0))
+    {
+        PG_RETURN_NULL();
+    }
+    agt_arg = AG_GET_ARG_AGTYPE_P(0);
+    /* check for an array */
+    if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg))
+        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("toStringList() argument must resolve to a list 
or null")));
+
+    count = AGT_ROOT_COUNT(agt_arg);
+
+    /* if we have an empty list or only one element in the list, return null */
+    if (count == 0)
+        PG_RETURN_NULL();
+
+    /* clear the result structure */
+    MemSet(&agis_result, 0, sizeof(agtype_in_state));
+
+    /* push the beginning of the array */
+    agis_result.res = push_agtype_value(&agis_result.parse_state,
+                                        WAGT_BEGIN_ARRAY, NULL);
+
+    /* iterate through the list */
+    for (i = 0; i < count; i++)
+    {
+        // TODO: check element's type, it's value, and convert it to string if 
possible.
+        elem = get_ith_agtype_value_from_container(&agt_arg->root, i);
+        string_elem.type = AGTV_STRING;
+
+        switch (elem->type)
+        {
+        case AGTV_STRING:
+
+            if(!elem)
+            {
+                string_elem.type = AGTV_NULL;
+
+                agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_ELEM, &string_elem);
+            }
+
+            string_elem.val.string.val = elem->val.string.val;
+            string_elem.val.string.len = elem->val.string.len;
+
+            agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_ELEM, &string_elem);
+
+            break;
+
+        case AGTV_FLOAT:
+            
+            sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value);
+            string_elem.val.string.val = pstrdup(buffer);
+            string_elem.val.string.len = strlen(buffer);
+
+            agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_ELEM, &string_elem);
+
+            break; 
+
+        case AGTV_INTEGER:
+
+            sprintf(buffer, "%ld", elem->val.int_value);
+            string_elem.val.string.val = pstrdup(buffer);
+            string_elem.val.string.len = strlen(buffer);
+
+            agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_ELEM, &string_elem);
+
+            break;
+
+        default:
+
+            string_elem.type = AGTV_NULL;
+            agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_ELEM, &string_elem);
+
+            break;
+        }
+    }
+    agis_result.res = push_agtype_value(&agis_result.parse_state, 
WAGT_END_ARRAY, NULL);
+
+    PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res));
+}
+
 static agtype_iterator *get_next_list_element(agtype_iterator *it,
                            agtype_container *agtc, agtype_value *elem)
 {

Reply via email to