/*
 * For PostgreSQL Database Management System:
 * (formerly known as Postgres, then as Postgres95)
 *
 * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group
 *
 * Portions Copyright (c) 1994, The Regents of the University of California
 *
 * Permission to use, copy, modify, and distribute this software and its documentation for any purpose,
 * without fee, and without a written agreement is hereby granted, provided that the above copyright notice
 * and this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT,
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY
 * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA
 * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

/*
 * converting between agtype and agtype_values, and iterating.
 *
 * Portions Copyright (c) 2014-2018, PostgreSQL Global Development Group
 */

#include "postgres.h"

#include <math.h>

#include "access/hash.h"
#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/varlena.h"

#include "utils/agtype_ext.h"

/*
 * Maximum number of elements in an array (or key/value pairs in an object).
 * This is limited by two things: the size of the agtentry array must fit
 * in MaxAllocSize, and the number of elements (or pairs) must fit in the bits
 * reserved for that in the agtype_container.header field.
 *
 * (The total size of an array's or object's elements is also limited by
 * AGTENTRY_OFFLENMASK, but we're not concerned about that here.)
 */
#define AGTYPE_MAX_ELEMS (Min(MaxAllocSize / sizeof(agtype_value), AGT_CMASK))
#define AGTYPE_MAX_PAIRS (Min(MaxAllocSize / sizeof(agtype_pair), AGT_CMASK))

static void fill_agtype_value(agtype_container *container, int index,
                              char *base_addr, uint32 offset,
                              agtype_value *result);
static bool equals_agtype_scalar_value(agtype_value *a, agtype_value *b);
static agtype *convert_to_agtype(agtype_value *val);
static void convert_agtype_value(StringInfo buffer, agtentry *header,
                                 agtype_value *val, int level);
static void convert_agtype_array(StringInfo buffer, agtentry *pheader,
                                 agtype_value *val, int level);
static void convert_agtype_object(StringInfo buffer, agtentry *pheader,
                                  agtype_value *val, int level);
static void convert_agtype_scalar(StringInfo buffer, agtentry *entry,
                                  agtype_value *scalar_val);

static void append_to_buffer(StringInfo buffer, const char *data, int len);
static void copy_to_buffer(StringInfo buffer, int offset, const char *data,
                           int len);

static agtype_iterator *iterator_from_container(agtype_container *container,
                                                agtype_iterator *parent);
static agtype_iterator *free_and_get_parent(agtype_iterator *it);
static agtype_parse_state *push_state(agtype_parse_state **pstate);
static void append_key(agtype_parse_state *pstate, agtype_value *string);
static void append_value(agtype_parse_state *pstate, agtype_value *scalar_val);
static void append_element(agtype_parse_state *pstate,
                           agtype_value *scalar_val);
static int length_compare_agtype_string_value(const void *a, const void *b);
static int length_compare_agtype_pair(const void *a, const void *b,
                                      void *binequal);
static agtype_value *push_agtype_value_scalar(agtype_parse_state **pstate,
                                              agtype_iterator_token seq,
                                              agtype_value *scalar_val);
static int compare_two_floats_orderability(float8 lhs, float8 rhs);
static int get_type_sort_priority(enum agtype_value_type type);

/*
 * Turn an in-memory agtype_value into an agtype for on-disk storage.
 *
 * There isn't an agtype_to_agtype_value(), because generally we find it more
 * convenient to directly iterate through the agtype representation and only
 * really convert nested scalar values.  agtype_iterator_next() does this, so
 * that clients of the iteration code don't have to directly deal with the
 * binary representation (agtype_deep_contains() is a notable exception,
 * although all exceptions are internal to this module).  In general, functions
 * that accept an agtype_value argument are concerned with the manipulation of
 * scalar values, or simple containers of scalar values, where it would be
 * inconvenient to deal with a great amount of other state.
 */
agtype *agtype_value_to_agtype(agtype_value *val)
{
    agtype *out;

    if (IS_A_AGTYPE_SCALAR(val))
    {
        /* Scalar value */
        agtype_parse_state *pstate = NULL;
        agtype_value *res;
        agtype_value scalar_array;

        scalar_array.type = AGTV_ARRAY;
        scalar_array.val.array.raw_scalar = true;
        scalar_array.val.array.num_elems = 1;

        push_agtype_value(&pstate, WAGT_BEGIN_ARRAY, &scalar_array);
        push_agtype_value(&pstate, WAGT_ELEM, val);
        res = push_agtype_value(&pstate, WAGT_END_ARRAY, NULL);

        out = convert_to_agtype(res);
    }
    else if (val->type == AGTV_OBJECT || val->type == AGTV_ARRAY)
    {
        out = convert_to_agtype(val);
    }
    else
    {
        Assert(val->type == AGTV_BINARY);
        out = palloc(VARHDRSZ + val->val.binary.len);
        SET_VARSIZE(out, VARHDRSZ + val->val.binary.len);
        memcpy(VARDATA(out), val->val.binary.data, val->val.binary.len);
    }

    return out;
}

/*
 * Get the offset of the variable-length portion of an agtype node within
 * the variable-length-data part of its container.  The node is identified
 * by index within the container's agtentry array.
 */
uint32 get_agtype_offset(const agtype_container *agtc, int index)
{
    uint32 offset = 0;
    int i;

    /*
     * Start offset of this entry is equal to the end offset of the previous
     * entry.  Walk backwards to the most recent entry stored as an end
     * offset, returning that offset plus any lengths in between.
     */
    for (i = index - 1; i >= 0; i--)
    {
        offset += AGTE_OFFLENFLD(agtc->children[i]);
        if (AGTE_HAS_OFF(agtc->children[i]))
            break;
    }

    return offset;
}

/*
 * Get the length of the variable-length portion of an agtype node.
 * The node is identified by index within the container's agtentry array.
 */
uint32 get_agtype_length(const agtype_container *agtc, int index)
{
    uint32 off;
    uint32 len;

    /*
     * If the length is stored directly in the agtentry, just return it.
     * Otherwise, get the begin offset of the entry, and subtract that from
     * the stored end+1 offset.
     */
    if (AGTE_HAS_OFF(agtc->children[index]))
    {
        off = get_agtype_offset(agtc, index);
        len = AGTE_OFFLENFLD(agtc->children[index]) - off;
    }
    else
    {
        len = AGTE_OFFLENFLD(agtc->children[index]);
    }

    return len;
}

/*
 * Helper function to generate the sort priority of a type. Larger
 * numbers have higher priority.
 */
static int get_type_sort_priority(enum agtype_value_type type)
{
    if (type == AGTV_PATH)
    {
        return 0;
    }
    if (type == AGTV_EDGE)
    {
        return 1;
    }
    if (type == AGTV_VERTEX)
    {
        return 2;
    }
    if (type == AGTV_OBJECT)
    {
        return 3;
    }
    if (type == AGTV_ARRAY)
    {
        return 4;
    }
    if (type == AGTV_STRING)
    {
        return 5;
    }
    if (type == AGTV_BOOL)
    {
        return 6;
    }
    if (type == AGTV_NUMERIC || type == AGTV_INTEGER || type == AGTV_FLOAT)
    {
        return 7;
    }
    if (type == AGTV_NULL)
    {
        return 8;
    }
    return -1;
}

/*
 * BT comparator worker function.  Returns an integer less than, equal to, or
 * greater than zero, indicating whether a is less than, equal to, or greater
 * than b.  Consistent with the requirements for a B-Tree operator class
 *
 * Strings are compared lexically, in contrast with other places where we use a
 * much simpler comparator logic for searching through Strings.  Since this is
 * called from B-Tree support function 1, we're careful about not leaking
 * memory here.
 */
int compare_agtype_containers_orderability(agtype_container *a,
                                           agtype_container *b)
{
    agtype_iterator *ita;
    agtype_iterator *itb;
    int res = 0;

    ita = agtype_iterator_init(a);
    itb = agtype_iterator_init(b);

    do
    {
        agtype_value va;
        agtype_value vb;
        agtype_iterator_token ra;
        agtype_iterator_token rb;

        ra = agtype_iterator_next(&ita, &va, false);
        rb = agtype_iterator_next(&itb, &vb, false);

        if (ra == rb)
        {
            if (ra == WAGT_DONE)
            {
                /* Decisively equal */
                break;
            }

            if (ra == WAGT_END_ARRAY || ra == WAGT_END_OBJECT)
            {
                /*
                 * There is no array or object to compare at this stage of
                 * processing.  AGTV_ARRAY/AGTV_OBJECT values are compared
                 * initially, at the WAGT_BEGIN_ARRAY and WAGT_BEGIN_OBJECT
                 * tokens.
                 */
                continue;
            }

            if ((va.type == vb.type) ||
                ((va.type == AGTV_INTEGER || va.type == AGTV_FLOAT ||
                  va.type == AGTV_NUMERIC) &&
                 (vb.type == AGTV_INTEGER || vb.type == AGTV_FLOAT ||
                  vb.type == AGTV_NUMERIC)))
            {
                switch (va.type)
                {
                case AGTV_STRING:
                case AGTV_NULL:
                case AGTV_NUMERIC:
                case AGTV_BOOL:
                case AGTV_INTEGER:
                case AGTV_FLOAT:
                case AGTV_EDGE:
                case AGTV_VERTEX:
                case AGTV_PATH:
                    res = compare_agtype_scalar_values(&va, &vb);
                    break;
                case AGTV_ARRAY:

                    /*
                     * This could be a "raw scalar" pseudo array.  That's
                     * a special case here though, since we still want the
                     * general type-based comparisons to apply, and as far
                     * as we're concerned a pseudo array is just a scalar.
                     */
                    if (va.val.array.raw_scalar != vb.val.array.raw_scalar)
                    {
                        if (va.val.array.raw_scalar)
                        {
                            /* advance iterator ita and get contained type */
                            ra = agtype_iterator_next(&ita, &va, false);
                            res = (get_type_sort_priority(va.type) <
                                   get_type_sort_priority(vb.type)) ?
                                      -1 :
                                      1;
                        }
                        else
                        {
                            /* advance iterator itb and get contained type */
                            rb = agtype_iterator_next(&itb, &vb, false);
                            res = (get_type_sort_priority(va.type) <
                                   get_type_sort_priority(vb.type)) ?
                                      -1 :
                                      1;
                        }
                    }
                    break;
                case AGTV_OBJECT:
                    break;
                case AGTV_BINARY:
                    ereport(ERROR, (errmsg("unexpected AGTV_BINARY value")));
                }
            }
            else
            {
                /* Type-defined order */
                res = (get_type_sort_priority(va.type) <
                       get_type_sort_priority(vb.type)) ?
                          -1 :
                          1;
            }
        }
        else
        {
            /*
             * It's safe to assume that the types differed, and that the va
             * and vb values passed were set.
             *
             * If the two values were of the same container type, then there'd
             * have been a chance to observe the variation in the number of
             * elements/pairs (when processing WAGT_BEGIN_OBJECT, say). They're
             * either two heterogeneously-typed containers, or a container and
             * some scalar type.
             */

            /*
             * Check for the premature array or object end.
             * If left side is shorter, less than.
             */
            if (ra == WAGT_END_ARRAY || ra == WAGT_END_OBJECT)
            {
                res = -1;
                break;
            }
            /* If right side is shorter, greater than */
            if (rb == WAGT_END_ARRAY || rb == WAGT_END_OBJECT)
            {
                res = 1;
                break;
            }

            /* Correction step because AGTV_ARRAY might be there just because of the container type */
            /* Case 1: left side is assigned to an array, right is an object */
            if(va.type == AGTV_ARRAY && vb.type == AGTV_OBJECT)
            {
                ra = agtype_iterator_next(&ita, &va, false);
            }
            /* Case 2: left side is an object, right side is assigned to an array */
            else if(va.type == AGTV_OBJECT && vb.type == AGTV_ARRAY)
            {
                rb = agtype_iterator_next(&itb, &vb, false);
            }
            
            Assert(va.type != vb.type);
            Assert(va.type != AGTV_BINARY);
            Assert(vb.type != AGTV_BINARY);
            /* Type-defined order */
            res = (get_type_sort_priority(va.type) <
                   get_type_sort_priority(vb.type)) ?
                      -1 :
                      1;
        }
    } while (res == 0);

    while (ita != NULL)
    {
        agtype_iterator *i = ita->parent;

        pfree(ita);
        ita = i;
    }
    while (itb != NULL)
    {
        agtype_iterator *i = itb->parent;

        pfree(itb);
        itb = i;
    }

    return res;
}

/*
 * Find value in object (i.e. the "value" part of some key/value pair in an
 * object), or find a matching element if we're looking through an array.  Do
 * so on the basis of equality of the object keys only, or alternatively
 * element values only, with a caller-supplied value "key".  The "flags"
 * argument allows the caller to specify which container types are of interest.
 *
 * This exported utility function exists to facilitate various cases concerned
 * with "containment".  If asked to look through an object, the caller had
 * better pass an agtype String, because their keys can only be strings.
 * Otherwise, for an array, any type of agtype_value will do.
 *
 * In order to proceed with the search, it is necessary for callers to have
 * both specified an interest in exactly one particular container type with an
 * appropriate flag, as well as having the pointed-to agtype container be of
 * one of those same container types at the top level. (Actually, we just do
 * whichever makes sense to save callers the trouble of figuring it out - at
 * most one can make sense, because the container either points to an array
 * (possibly a "raw scalar" pseudo array) or an object.)
 *
 * Note that we can return an AGTV_BINARY agtype_value if this is called on an
 * object, but we never do so on an array.  If the caller asks to look through
 * a container type that is not of the type pointed to by the container,
 * immediately fall through and return NULL.  If we cannot find the value,
 * return NULL.  Otherwise, return palloc()'d copy of value.
 */
agtype_value *find_agtype_value_from_container(agtype_container *container,
                                               uint32 flags, agtype_value *key)
{
    agtentry *children = container->children;
    int count = AGTYPE_CONTAINER_SIZE(container);
    agtype_value *result;

    Assert((flags & ~(AGT_FARRAY | AGT_FOBJECT)) == 0);

    /* Quick out without a palloc cycle if object/array is empty */
    if (count <= 0)
    {
        return NULL;
    }

    result = palloc(sizeof(agtype_value));

    if ((flags & AGT_FARRAY) && AGTYPE_CONTAINER_IS_ARRAY(container))
    {
        char *base_addr = (char *)(children + count);
        uint32 offset = 0;
        int i;

        for (i = 0; i < count; i++)
        {
            fill_agtype_value(container, i, base_addr, offset, result);

            if (key->type == result->type)
            {
                if (equals_agtype_scalar_value(key, result))
                    return result;
            }

            AGTE_ADVANCE_OFFSET(offset, children[i]);
        }
    }
    else if ((flags & AGT_FOBJECT) && AGTYPE_CONTAINER_IS_OBJECT(container))
    {
        /* Since this is an object, account for *Pairs* of AGTentrys */
        char *base_addr = (char *)(children + count * 2);
        uint32 stop_low = 0;
        uint32 stop_high = count;

        /* Object key passed by caller must be a string */
        Assert(key->type == AGTV_STRING);

        /* Binary search on object/pair keys *only* */
        while (stop_low < stop_high)
        {
            uint32 stop_middle;
            int difference;
            agtype_value candidate;

            stop_middle = stop_low + (stop_high - stop_low) / 2;

            candidate.type = AGTV_STRING;
            candidate.val.string.val =
                base_addr + get_agtype_offset(container, stop_middle);
            candidate.val.string.len = get_agtype_length(container,
                                                         stop_middle);

            difference = length_compare_agtype_string_value(&candidate, key);

            if (difference == 0)
            {
                /* Found our key, return corresponding value */
                int index = stop_middle + count;

                fill_agtype_value(container, index, base_addr,
                                  get_agtype_offset(container, index), result);

                return result;
            }
            else
            {
                if (difference < 0)
                    stop_low = stop_middle + 1;
                else
                    stop_high = stop_middle;
            }
        }
    }

    /* Not found */
    pfree(result);
    return NULL;
}

/*
 * Get i-th value of an agtype array.
 *
 * Returns palloc()'d copy of the value, or NULL if it does not exist.
 */
agtype_value *get_ith_agtype_value_from_container(agtype_container *container,
                                                  uint32 i)
{
    agtype_value *result;
    char *base_addr;
    uint32 nelements;

    if (!AGTYPE_CONTAINER_IS_ARRAY(container))
        ereport(ERROR, (errmsg("container is not an agtype array")));

    nelements = AGTYPE_CONTAINER_SIZE(container);
    base_addr = (char *)&container->children[nelements];

    if (i >= nelements)
        return NULL;

    result = palloc(sizeof(agtype_value));

    fill_agtype_value(container, i, base_addr, get_agtype_offset(container, i),
                      result);

    return result;
}

/*
 * A helper function to fill in an agtype_value to represent an element of an
 * array, or a key or value of an object.
 *
 * The node's agtentry is at container->children[index], and its variable-length
 * data is at base_addr + offset.  We make the caller determine the offset
 * since in many cases the caller can amortize that work across multiple
 * children.  When it can't, it can just call get_agtype_offset().
 *
 * A nested array or object will be returned as AGTV_BINARY, ie. it won't be
 * expanded.
 */
static void fill_agtype_value(agtype_container *container, int index,
                              char *base_addr, uint32 offset,
                              agtype_value *result)
{
    agtentry entry = container->children[index];

    if (AGTE_IS_NULL(entry))
    {
        result->type = AGTV_NULL;
    }
    else if (AGTE_IS_STRING(entry))
    {
        char *string_val;
        int string_len;

        result->type = AGTV_STRING;
        /* get the position and length of the string */
        string_val = base_addr + offset;
        string_len = get_agtype_length(container, index);
        /* we need to do a deep copy of the string value */
        result->val.string.val = pnstrdup(string_val, string_len);
        result->val.string.len = string_len;
        Assert(result->val.string.len >= 0);
    }
    else if (AGTE_IS_NUMERIC(entry))
    {
        Numeric numeric;
        Numeric numeric_copy;

        result->type = AGTV_NUMERIC;
        /* we need to do a deep copy here */
        numeric = (Numeric)(base_addr + INTALIGN(offset));
        numeric_copy = (Numeric) palloc(VARSIZE(numeric));
        memcpy(numeric_copy, numeric, VARSIZE(numeric));
        result->val.numeric = numeric_copy;
    }
    /*
     * If this is an agtype.
     * This is needed because we allow the original jsonb type to be
     * passed in.
     */
    else if (AGTE_IS_AGTYPE(entry))
    {
        ag_deserialize_extended_type(base_addr, offset, result);
    }
    else if (AGTE_IS_BOOL_TRUE(entry))
    {
        result->type = AGTV_BOOL;
        result->val.boolean = true;
    }
    else if (AGTE_IS_BOOL_FALSE(entry))
    {
        result->type = AGTV_BOOL;
        result->val.boolean = false;
    }
    else
    {
        Assert(AGTE_IS_CONTAINER(entry));
        result->type = AGTV_BINARY;
        /* Remove alignment padding from data pointer and length */
        result->val.binary.data =
            (agtype_container *)(base_addr + INTALIGN(offset));
        result->val.binary.len = get_agtype_length(container, index) -
                                 (INTALIGN(offset) - offset);
    }
}

/*
 * Push agtype_value into agtype_parse_state.
 *
 * Used when parsing agtype tokens to form agtype, or when converting an
 * in-memory agtype_value to an agtype.
 *
 * Initial state of *agtype_parse_state is NULL, since it'll be allocated here
 * originally (caller will get agtype_parse_state back by reference).
 *
 * Only sequential tokens pertaining to non-container types should pass an
 * agtype_value.  There is one exception -- WAGT_BEGIN_ARRAY callers may pass a
 * "raw scalar" pseudo array to append it - the actual scalar should be passed
 * next and it will be added as the only member of the array.
 *
 * Values of type AGTV_BINARY, which are rolled up arrays and objects,
 * are unpacked before being added to the result.
 */
agtype_value *push_agtype_value(agtype_parse_state **pstate,
                                agtype_iterator_token seq,
                                agtype_value *agtval)
{
    agtype_iterator *it;
    agtype_value *res = NULL;
    agtype_value v;
    agtype_iterator_token tok;

    if (!agtval || (seq != WAGT_ELEM && seq != WAGT_VALUE) ||
        agtval->type != AGTV_BINARY)
    {
        /* drop through */
        return push_agtype_value_scalar(pstate, seq, agtval);
    }

    /* unpack the binary and add each piece to the pstate */
    it = agtype_iterator_init(agtval->val.binary.data);
    while ((tok = agtype_iterator_next(&it, &v, false)) != WAGT_DONE)
    {
        res = push_agtype_value_scalar(pstate, tok,
                                       tok < WAGT_BEGIN_ARRAY ? &v : NULL);
    }

    return res;
}

/*
 * Do the actual pushing, with only scalar or pseudo-scalar-array values
 * accepted.
 */
static agtype_value *push_agtype_value_scalar(agtype_parse_state **pstate,
                                              agtype_iterator_token seq,
                                              agtype_value *scalar_val)
{
    agtype_value *result = NULL;

    switch (seq)
    {
    case WAGT_BEGIN_ARRAY:
        Assert(!scalar_val || scalar_val->val.array.raw_scalar);
        *pstate = push_state(pstate);
        result = &(*pstate)->cont_val;
        (*pstate)->cont_val.type = AGTV_ARRAY;
        (*pstate)->cont_val.val.array.num_elems = 0;
        (*pstate)->cont_val.val.array.raw_scalar =
            (scalar_val && scalar_val->val.array.raw_scalar);
        if (scalar_val && scalar_val->val.array.num_elems > 0)
        {
            /* Assume that this array is still really a scalar */
            Assert(scalar_val->type == AGTV_ARRAY);
            (*pstate)->size = scalar_val->val.array.num_elems;
        }
        else
        {
            (*pstate)->size = 4;
        }
        (*pstate)->cont_val.val.array.elems =
            palloc(sizeof(agtype_value) * (*pstate)->size);
        (*pstate)->last_updated_value = NULL;
        break;
    case WAGT_BEGIN_OBJECT:
        Assert(!scalar_val);
        *pstate = push_state(pstate);
        result = &(*pstate)->cont_val;
        (*pstate)->cont_val.type = AGTV_OBJECT;
        (*pstate)->cont_val.val.object.num_pairs = 0;
        (*pstate)->size = 4;
        (*pstate)->cont_val.val.object.pairs =
            palloc(sizeof(agtype_pair) * (*pstate)->size);
        (*pstate)->last_updated_value = NULL;
        break;
    case WAGT_KEY:
        Assert(scalar_val->type == AGTV_STRING);
        append_key(*pstate, scalar_val);
        break;
    case WAGT_VALUE:
        Assert(IS_A_AGTYPE_SCALAR(scalar_val));
        append_value(*pstate, scalar_val);
        break;
    case WAGT_ELEM:
        Assert(IS_A_AGTYPE_SCALAR(scalar_val));
        append_element(*pstate, scalar_val);
        break;
    case WAGT_END_OBJECT:
        uniqueify_agtype_object(&(*pstate)->cont_val);
        /* fall through! */
    case WAGT_END_ARRAY:
        /* Steps here common to WAGT_END_OBJECT case */
        Assert(!scalar_val);
        result = &(*pstate)->cont_val;

        /*
         * Pop stack and push current array/object as value in parent
         * array/object
         */
        *pstate = (*pstate)->next;
        if (*pstate)
        {
            switch ((*pstate)->cont_val.type)
            {
            case AGTV_ARRAY:
                append_element(*pstate, result);
                break;
            case AGTV_OBJECT:
                append_value(*pstate, result);
                break;
            default:
                ereport(ERROR, (errmsg("invalid agtype container type %d",
                                       (*pstate)->cont_val.type)));
            }
        }
        break;
    default:
        ereport(ERROR,
                (errmsg("unrecognized agtype sequential processing token")));
    }

    return result;
}

/*
 * push_agtype_value() worker:  Iteration-like forming of agtype
 */
static agtype_parse_state *push_state(agtype_parse_state **pstate)
{
    agtype_parse_state *ns = palloc(sizeof(agtype_parse_state));

    ns->next = *pstate;
    return ns;
}

/*
 * push_agtype_value() worker:  Append a pair key to state when generating
 *                              agtype
 */
static void append_key(agtype_parse_state *pstate, agtype_value *string)
{
    agtype_value *object = &pstate->cont_val;

    Assert(object->type == AGTV_OBJECT);
    Assert(string->type == AGTV_STRING);

    if (object->val.object.num_pairs >= AGTYPE_MAX_PAIRS)
        ereport(
            ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
             errmsg(
                 "number of agtype object pairs exceeds the maximum allowed (%zu)",
                 AGTYPE_MAX_PAIRS)));

    if (object->val.object.num_pairs >= pstate->size)
    {
        pstate->size *= 2;
        object->val.object.pairs = repalloc(
            object->val.object.pairs, sizeof(agtype_pair) * pstate->size);
    }

    object->val.object.pairs[object->val.object.num_pairs].key = *string;
    object->val.object.pairs[object->val.object.num_pairs].order =
        object->val.object.num_pairs;
}

/*
 * push_agtype_value() worker:  Append a pair value to state when generating an
 *                              agtype
 */
static void append_value(agtype_parse_state *pstate, agtype_value *scalar_val)
{
    agtype_value *object = &pstate->cont_val;

    Assert(object->type == AGTV_OBJECT);

    object->val.object.pairs[object->val.object.num_pairs].value = *scalar_val;

    pstate->last_updated_value =
        &object->val.object.pairs[object->val.object.num_pairs++].value;
}

/*
 * push_agtype_value() worker:  Append an element to state when generating an
 *                              agtype
 */
static void append_element(agtype_parse_state *pstate,
                           agtype_value *scalar_val)
{
    agtype_value *array = &pstate->cont_val;

    Assert(array->type == AGTV_ARRAY);

    if (array->val.array.num_elems >= AGTYPE_MAX_ELEMS)
        ereport(
            ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
             errmsg(
                 "number of agtype array elements exceeds the maximum allowed (%zu)",
                 AGTYPE_MAX_ELEMS)));

    if (array->val.array.num_elems >= pstate->size)
    {
        pstate->size *= 2;
        array->val.array.elems = repalloc(array->val.array.elems,
                                          sizeof(agtype_value) * pstate->size);
    }

    array->val.array.elems[array->val.array.num_elems] = *scalar_val;
    pstate->last_updated_value =
        &array->val.array.elems[array->val.array.num_elems++];
}

/*
 * Given an agtype_container, expand to agtype_iterator to iterate over items
 * fully expanded to in-memory representation for manipulation.
 *
 * See agtype_iterator_next() for notes on memory management.
 */
agtype_iterator *agtype_iterator_init(agtype_container *container)
{
    return iterator_from_container(container, NULL);
}

/*
 * Get next agtype_value while iterating
 *
 * Caller should initially pass their own, original iterator.  They may get
 * back a child iterator palloc()'d here instead.  The function can be relied
 * on to free those child iterators, lest the memory allocated for highly
 * nested objects become unreasonable, but only if callers don't end iteration
 * early (by breaking upon having found something in a search, for example).
 *
 * Callers in such a scenario, that are particularly sensitive to leaking
 * memory in a long-lived context may walk the ancestral tree from the final
 * iterator we left them with to its oldest ancestor, pfree()ing as they go.
 * They do not have to free any other memory previously allocated for iterators
 * but not accessible as direct ancestors of the iterator they're last passed
 * back.
 *
 * Returns "agtype sequential processing" token value.  Iterator "state"
 * reflects the current stage of the process in a less granular fashion, and is
 * mostly used here to track things internally with respect to particular
 * iterators.
 *
 * Clients of this function should not have to handle any AGTV_BINARY values
 * (since recursive calls will deal with this), provided skip_nested is false.
 * It is our job to expand the AGTV_BINARY representation without bothering
 * them with it.  However, clients should not take it upon themselves to touch
 * array or Object element/pair buffers, since their element/pair pointers are
 * garbage.  Also, *val will not be set when returning WAGT_END_ARRAY or
 * WAGT_END_OBJECT, on the assumption that it's only useful to access values
 * when recursing in.
 */
agtype_iterator_token agtype_iterator_next(agtype_iterator **it,
                                           agtype_value *val, bool skip_nested)
{
    if (*it == NULL)
        return WAGT_DONE;

    /*
     * When stepping into a nested container, we jump back here to start
     * processing the child. We will not recurse further in one call, because
     * processing the child will always begin in AGTI_ARRAY_START or
     * AGTI_OBJECT_START state.
     */
recurse:
    switch ((*it)->state)
    {
    case AGTI_ARRAY_START:
        /* Set v to array on first array call */
        val->type = AGTV_ARRAY;
        val->val.array.num_elems = (*it)->num_elems;

        /*
         * v->val.array.elems is not actually set, because we aren't doing
         * a full conversion
         */
        val->val.array.raw_scalar = (*it)->is_scalar;
        (*it)->curr_index = 0;
        (*it)->curr_data_offset = 0;
        (*it)->curr_value_offset = 0; /* not actually used */
        /* Set state for next call */
        (*it)->state = AGTI_ARRAY_ELEM;
        return WAGT_BEGIN_ARRAY;

    case AGTI_ARRAY_ELEM:
        if ((*it)->curr_index >= (*it)->num_elems)
        {
            /*
             * All elements within array already processed.  Report this
             * to caller, and give it back original parent iterator (which
             * independently tracks iteration progress at its level of
             * nesting).
             */
            *it = free_and_get_parent(*it);
            return WAGT_END_ARRAY;
        }

        fill_agtype_value((*it)->container, (*it)->curr_index,
                          (*it)->data_proper, (*it)->curr_data_offset, val);

        AGTE_ADVANCE_OFFSET((*it)->curr_data_offset,
                            (*it)->children[(*it)->curr_index]);
        (*it)->curr_index++;

        if (!IS_A_AGTYPE_SCALAR(val) && !skip_nested)
        {
            /* Recurse into container. */
            *it = iterator_from_container(val->val.binary.data, *it);
            goto recurse;
        }
        else
        {
            /*
             * Scalar item in array, or a container and caller didn't want
             * us to recurse into it.
             */
            return WAGT_ELEM;
        }

    case AGTI_OBJECT_START:
        /* Set v to object on first object call */
        val->type = AGTV_OBJECT;
        val->val.object.num_pairs = (*it)->num_elems;

        /*
         * v->val.object.pairs is not actually set, because we aren't
         * doing a full conversion
         */
        (*it)->curr_index = 0;
        (*it)->curr_data_offset = 0;
        (*it)->curr_value_offset = get_agtype_offset((*it)->container,
                                                     (*it)->num_elems);
        /* Set state for next call */
        (*it)->state = AGTI_OBJECT_KEY;
        return WAGT_BEGIN_OBJECT;

    case AGTI_OBJECT_KEY:
        if ((*it)->curr_index >= (*it)->num_elems)
        {
            /*
             * All pairs within object already processed.  Report this to
             * caller, and give it back original containing iterator
             * (which independently tracks iteration progress at its level
             * of nesting).
             */
            *it = free_and_get_parent(*it);
            return WAGT_END_OBJECT;
        }
        else
        {
            /* Return key of a key/value pair.  */
            fill_agtype_value((*it)->container, (*it)->curr_index,
                              (*it)->data_proper, (*it)->curr_data_offset,
                              val);
            if (val->type != AGTV_STRING)
                ereport(ERROR,
                        (errmsg("unexpected agtype type as object key %d",
                                val->type)));

            /* Set state for next call */
            (*it)->state = AGTI_OBJECT_VALUE;
            return WAGT_KEY;
        }

    case AGTI_OBJECT_VALUE:
        /* Set state for next call */
        (*it)->state = AGTI_OBJECT_KEY;

        fill_agtype_value((*it)->container,
                          (*it)->curr_index + (*it)->num_elems,
                          (*it)->data_proper, (*it)->curr_value_offset, val);

        AGTE_ADVANCE_OFFSET((*it)->curr_data_offset,
                            (*it)->children[(*it)->curr_index]);
        AGTE_ADVANCE_OFFSET(
            (*it)->curr_value_offset,
            (*it)->children[(*it)->curr_index + (*it)->num_elems]);
        (*it)->curr_index++;

        /*
         * Value may be a container, in which case we recurse with new,
         * child iterator (unless the caller asked not to, by passing
         * skip_nested).
         */
        if (!IS_A_AGTYPE_SCALAR(val) && !skip_nested)
        {
            *it = iterator_from_container(val->val.binary.data, *it);
            goto recurse;
        }
        else
        {
            return WAGT_VALUE;
        }
    }

    ereport(ERROR, (errmsg("invalid iterator state %d", (*it)->state)));
    return -1;
}

/*
 * Initialize an iterator for iterating all elements in a container.
 */
static agtype_iterator *iterator_from_container(agtype_container *container,
                                                agtype_iterator *parent)
{
    agtype_iterator *it;

    it = palloc0(sizeof(agtype_iterator));
    it->container = container;
    it->parent = parent;
    it->num_elems = AGTYPE_CONTAINER_SIZE(container);

    /* Array starts just after header */
    it->children = container->children;

    switch (container->header & (AGT_FARRAY | AGT_FOBJECT))
    {
    case AGT_FARRAY:
        it->data_proper = (char *)it->children +
                          it->num_elems * sizeof(agtentry);
        it->is_scalar = AGTYPE_CONTAINER_IS_SCALAR(container);
        /* This is either a "raw scalar", or an array */
        Assert(!it->is_scalar || it->num_elems == 1);

        it->state = AGTI_ARRAY_START;
        break;

    case AGT_FOBJECT:
        it->data_proper = (char *)it->children +
                          it->num_elems * sizeof(agtentry) * 2;
        it->state = AGTI_OBJECT_START;
        break;

    default:
        ereport(ERROR,
                (errmsg("unknown type of agtype container %d",
                        container->header & (AGT_FARRAY | AGT_FOBJECT))));
    }

    return it;
}

/*
 * agtype_iterator_next() worker: Return parent, while freeing memory for
 *                                current iterator
 */
static agtype_iterator *free_and_get_parent(agtype_iterator *it)
{
    agtype_iterator *v = it->parent;

    pfree(it);
    return v;
}

/*
 * Worker for "contains" operator's function
 *
 * Formally speaking, containment is top-down, unordered subtree isomorphism.
 *
 * Takes iterators that belong to some container type.  These iterators
 * "belong" to those values in the sense that they've just been initialized in
 * respect of them by the caller (perhaps in a nested fashion).
 *
 * "val" is lhs agtype, and m_contained is rhs agtype when called from top
 * level. We determine if m_contained is contained within val.
 */
bool agtype_deep_contains(agtype_iterator **val, agtype_iterator **m_contained)
{
    agtype_value vval;
    agtype_value vcontained;
    agtype_iterator_token rval;
    agtype_iterator_token rcont;

    /*
     * Guard against stack overflow due to overly complex agtype.
     *
     * Functions called here independently take this precaution, but that
     * might not be sufficient since this is also a recursive function.
     */
    check_stack_depth();

    rval = agtype_iterator_next(val, &vval, false);
    rcont = agtype_iterator_next(m_contained, &vcontained, false);

    if (rval != rcont)
    {
        /*
         * The differing return values can immediately be taken as indicating
         * two differing container types at this nesting level, which is
         * sufficient reason to give up entirely (but it should be the case
         * that they're both some container type).
         */
        Assert(rval == WAGT_BEGIN_OBJECT || rval == WAGT_BEGIN_ARRAY);
        Assert(rcont == WAGT_BEGIN_OBJECT || rcont == WAGT_BEGIN_ARRAY);
        return false;
    }
    else if (rcont == WAGT_BEGIN_OBJECT)
    {
        Assert(vval.type == AGTV_OBJECT);
        Assert(vcontained.type == AGTV_OBJECT);

        /*
         * If the lhs has fewer pairs than the rhs, it can't possibly contain
         * the rhs.  (This conclusion is safe only because we de-duplicate
         * keys in all agtype objects; thus there can be no corresponding
         * optimization in the array case.)  The case probably won't arise
         * often, but since it's such a cheap check we may as well make it.
         */
        if (vval.val.object.num_pairs < vcontained.val.object.num_pairs)
            return false;

        /* Work through rhs "is it contained within?" object */
        for (;;)
        {
            agtype_value *lhs_val; /* lhs_val is from pair in lhs object */

            rcont = agtype_iterator_next(m_contained, &vcontained, false);

            /*
             * When we get through caller's rhs "is it contained within?"
             * object without failing to find one of its values, it's
             * contained.
             */
            if (rcont == WAGT_END_OBJECT)
                return true;

            Assert(rcont == WAGT_KEY);

            /* First, find value by key... */
            lhs_val = find_agtype_value_from_container(
                (*val)->container, AGT_FOBJECT, &vcontained);

            if (!lhs_val)
                return false;

            /*
             * ...at this stage it is apparent that there is at least a key
             * match for this rhs pair.
             */
            rcont = agtype_iterator_next(m_contained, &vcontained, true);

            Assert(rcont == WAGT_VALUE);

            /*
             * Compare rhs pair's value with lhs pair's value just found using
             * key
             */
            if (lhs_val->type != vcontained.type)
            {
                return false;
            }
            else if (IS_A_AGTYPE_SCALAR(lhs_val))
            {
                if (!equals_agtype_scalar_value(lhs_val, &vcontained))
                    return false;
            }
            else
            {
                /* Nested container value (object or array) */
                agtype_iterator *nestval;
                agtype_iterator *nest_contained;

                Assert(lhs_val->type == AGTV_BINARY);
                Assert(vcontained.type == AGTV_BINARY);

                nestval = agtype_iterator_init(lhs_val->val.binary.data);
                nest_contained =
                    agtype_iterator_init(vcontained.val.binary.data);

                /*
                 * Match "value" side of rhs datum object's pair recursively.
                 * It's a nested structure.
                 *
                 * Note that nesting still has to "match up" at the right
                 * nesting sub-levels.  However, there need only be zero or
                 * more matching pairs (or elements) at each nesting level
                 * (provided the *rhs* pairs/elements *all* match on each
                 * level), which enables searching nested structures for a
                 * single String or other primitive type sub-datum quite
                 * effectively (provided the user constructed the rhs nested
                 * structure such that we "know where to look").
                 *
                 * In other words, the mapping of container nodes in the rhs
                 * "vcontained" agtype to internal nodes on the lhs is
                 * injective, and parent-child edges on the rhs must be mapped
                 * to parent-child edges on the lhs to satisfy the condition
                 * of containment (plus of course the mapped nodes must be
                 * equal).
                 */
                if (!agtype_deep_contains(&nestval, &nest_contained))
                    return false;
            }
        }
    }
    else if (rcont == WAGT_BEGIN_ARRAY)
    {
        agtype_value *lhs_conts = NULL;
        uint32 num_lhs_elems = vval.val.array.num_elems;

        Assert(vval.type == AGTV_ARRAY);
        Assert(vcontained.type == AGTV_ARRAY);

        /*
         * Handle distinction between "raw scalar" pseudo arrays, and real
         * arrays.
         *
         * A raw scalar may contain another raw scalar, and an array may
         * contain a raw scalar, but a raw scalar may not contain an array. We
         * don't do something like this for the object case, since objects can
         * only contain pairs, never raw scalars (a pair is represented by an
         * rhs object argument with a single contained pair).
         */
        if (vval.val.array.raw_scalar && !vcontained.val.array.raw_scalar)
            return false;

        /* Work through rhs "is it contained within?" array */
        for (;;)
        {
            rcont = agtype_iterator_next(m_contained, &vcontained, true);

            /*
             * When we get through caller's rhs "is it contained within?"
             * array without failing to find one of its values, it's
             * contained.
             */
            if (rcont == WAGT_END_ARRAY)
                return true;

            Assert(rcont == WAGT_ELEM);

            if (IS_A_AGTYPE_SCALAR(&vcontained))
            {
                if (!find_agtype_value_from_container((*val)->container,
                                                      AGT_FARRAY, &vcontained))
                    return false;
            }
            else
            {
                uint32 i;

                /*
                 * If this is first container found in rhs array (at this
                 * depth), initialize temp lhs array of containers
                 */
                if (lhs_conts == NULL)
                {
                    uint32 j = 0;

                    /* Make room for all possible values */
                    lhs_conts = palloc(sizeof(agtype_value) * num_lhs_elems);

                    for (i = 0; i < num_lhs_elems; i++)
                    {
                        /* Store all lhs elements in temp array */
                        rcont = agtype_iterator_next(val, &vval, true);
                        Assert(rcont == WAGT_ELEM);

                        if (vval.type == AGTV_BINARY)
                            lhs_conts[j++] = vval;
                    }

                    /* No container elements in temp array, so give up now */
                    if (j == 0)
                        return false;

                    /* We may have only partially filled array */
                    num_lhs_elems = j;
                }

                /* XXX: Nested array containment is O(N^2) */
                for (i = 0; i < num_lhs_elems; i++)
                {
                    /* Nested container value (object or array) */
                    agtype_iterator *nestval;
                    agtype_iterator *nest_contained;
                    bool contains;

                    nestval =
                        agtype_iterator_init(lhs_conts[i].val.binary.data);
                    nest_contained =
                        agtype_iterator_init(vcontained.val.binary.data);

                    contains = agtype_deep_contains(&nestval, &nest_contained);

                    if (nestval)
                        pfree(nestval);
                    if (nest_contained)
                        pfree(nest_contained);
                    if (contains)
                        break;
                }

                /*
                 * Report rhs container value is not contained if couldn't
                 * match rhs container to *some* lhs cont
                 */
                if (i == num_lhs_elems)
                    return false;
            }
        }
    }
    else
    {
        ereport(ERROR, (errmsg("invalid agtype container type")));
    }

    ereport(ERROR, (errmsg("unexpectedly fell off end of agtype container")));
    return false;
}

/*
 * Hash an agtype_value scalar value, mixing the hash value into an existing
 * hash provided by the caller.
 *
 * Some callers may wish to independently XOR in AGT_FOBJECT and AGT_FARRAY
 * flags.
 */
void agtype_hash_scalar_value(const agtype_value *scalar_val, uint32 *hash)
{
    uint32 tmp;

    /* Compute hash value for scalar_val */
    switch (scalar_val->type)
    {
    case AGTV_NULL:
        tmp = 0x01;
        break;
    case AGTV_STRING:
        tmp = DatumGetUInt32(
            hash_any((const unsigned char *)scalar_val->val.string.val,
                     scalar_val->val.string.len));
        break;
    case AGTV_NUMERIC:
        /* Must hash equal numerics to equal hash codes */
        tmp = DatumGetUInt32(DirectFunctionCall1(
            hash_numeric, NumericGetDatum(scalar_val->val.numeric)));
        break;
    case AGTV_BOOL:
        tmp = scalar_val->val.boolean ? 0x02 : 0x04;
        break;
    case AGTV_INTEGER:
        tmp = DatumGetUInt32(DirectFunctionCall1(
            hashint8, Int64GetDatum(scalar_val->val.int_value)));
        break;
    case AGTV_FLOAT:
        tmp = DatumGetUInt32(DirectFunctionCall1(
            hashfloat8, Float8GetDatum(scalar_val->val.float_value)));
        break;
    default:
        ereport(ERROR, (errmsg("invalid agtype scalar type %d to compute hash",
                               scalar_val->type)));
        tmp = 0; /* keep compiler quiet */
        break;
    }

    /*
     * Combine hash values of successive keys, values and elements by rotating
     * the previous value left 1 bit, then XOR'ing in the new
     * key/value/element's hash value.
     */
    *hash = (*hash << 1) | (*hash >> 31);
    *hash ^= tmp;
}

/*
 * Hash a value to a 64-bit value, with a seed. Otherwise, similar to
 * agtype_hash_scalar_value.
 */
void agtype_hash_scalar_value_extended(const agtype_value *scalar_val,
                                       uint64 *hash, uint64 seed)
{
    uint64 tmp = 0;

    switch (scalar_val->type)
    {
    case AGTV_NULL:
        tmp = seed + 0x01;
        break;
    case AGTV_STRING:
        tmp = DatumGetUInt64(hash_any_extended(
            (const unsigned char *)scalar_val->val.string.val,
            scalar_val->val.string.len, seed));
        break;
    case AGTV_NUMERIC:
        tmp = DatumGetUInt64(DirectFunctionCall2(
            hash_numeric_extended, NumericGetDatum(scalar_val->val.numeric),
            UInt64GetDatum(seed)));
        break;
    case AGTV_BOOL:
        if (seed)
        {
            tmp = DatumGetUInt64(DirectFunctionCall2(
                hashcharextended, BoolGetDatum(scalar_val->val.boolean),
                UInt64GetDatum(seed)));
        }
        else
        {
            tmp = scalar_val->val.boolean ? 0x02 : 0x04;
        }
        break;
    case AGTV_INTEGER:
        tmp = DatumGetUInt64(DirectFunctionCall2(
            hashint8extended, Int64GetDatum(scalar_val->val.int_value),
            UInt64GetDatum(seed)));
        break;
    case AGTV_FLOAT:
        tmp = DatumGetUInt64(DirectFunctionCall2(
            hashfloat8extended, Float8GetDatum(scalar_val->val.float_value),
            UInt64GetDatum(seed)));
        break;
    case AGTV_VERTEX:
    {
        graphid id;
        agtype_value *id_agt = GET_AGTYPE_VALUE_OBJECT_VALUE(scalar_val, "id");
        id = id_agt->val.int_value;
        tmp = DatumGetUInt64(DirectFunctionCall2(
            hashint8extended, Float8GetDatum(id), UInt64GetDatum(seed)));
        break;
    }
    case AGTV_EDGE:
    {
        graphid id;
        agtype_value *id_agt = GET_AGTYPE_VALUE_OBJECT_VALUE(scalar_val, "id");
        id = id_agt->val.int_value;
        tmp = DatumGetUInt64(DirectFunctionCall2(
            hashint8extended, Float8GetDatum(id), UInt64GetDatum(seed)));
        break;
    }
    case AGTV_PATH:
    {
        int i;
        for (i = 0; i < scalar_val->val.array.num_elems; i++)
        {
            agtype_value v;
            v = scalar_val->val.array.elems[i];
            agtype_hash_scalar_value_extended(&v, &tmp, seed);
        }
        break;
    }
    default:
        ereport(
            ERROR,
            (errmsg("invalid agtype scalar type %d to compute hash extended",
                    scalar_val->type)));
        break;
    }

    *hash = ROTATE_HIGH_AND_LOW_32BITS(*hash);
    *hash ^= tmp;
}

/*
 * Function to compare two floats, obviously. However, there are a few
 * special cases that we need to cover with regards to NaN and +/-Infinity.
 * NaN is not equal to any other number, including itself. However, for
 * ordering, we need to allow NaN = NaN and NaN > any number including
 * positive infinity -
 *
 *     -Infinity < any number < +Infinity < NaN
 *
 * Note: This is copied from float8_cmp_internal.
 * Note: Special float values can cause exceptions, hence the order of the
 *       comparisons.
 */
static int compare_two_floats_orderability(float8 lhs, float8 rhs)
{
    /*
     * We consider all NANs to be equal and larger than any non-NAN. This is
     * somewhat arbitrary; the important thing is to have a consistent sort
     * order.
     */
    if (isnan(lhs))
    {
        if (isnan(rhs))
            return 0; /* NAN = NAN */
        else
            return 1; /* NAN > non-NAN */
    }
    else if (isnan(rhs))
    {
        return -1; /* non-NAN < NAN */
    }
    else
    {
        if (lhs > rhs)
            return 1;
        else if (lhs < rhs)
            return -1;
        else
            return 0;
    }
}

/*
 * Are two scalar agtype_values of the same type a and b equal?
 */
static bool equals_agtype_scalar_value(agtype_value *a, agtype_value *b)
{
    /* if the values are of the same type */
    if (a->type == b->type)
    {
        switch (a->type)
        {
        case AGTV_NULL:
            return true;
        case AGTV_STRING:
            return length_compare_agtype_string_value(a, b) == 0;
        case AGTV_NUMERIC:
            return DatumGetBool(DirectFunctionCall2(
                numeric_eq, PointerGetDatum(a->val.numeric),
                PointerGetDatum(b->val.numeric)));
        case AGTV_BOOL:
            return a->val.boolean == b->val.boolean;
        case AGTV_INTEGER:
            return a->val.int_value == b->val.int_value;
        case AGTV_FLOAT:
            return a->val.float_value == b->val.float_value;
        case AGTV_VERTEX:
        {
            graphid a_graphid, b_graphid;
            a_graphid = a->val.object.pairs[0].value.val.int_value;
            b_graphid = b->val.object.pairs[0].value.val.int_value;

            return a_graphid == b_graphid;
        }

        default:
            ereport(ERROR, (errmsg("invalid agtype scalar type %d for equals",
                                   a->type)));
        }
    }
    /* otherwise, the values are of differing type */
    else
        ereport(ERROR, (errmsg("agtype input scalars must be of same type")));

    /* execution will never reach this point due to the ereport call */
    return false;
}

/*
 * Compare two scalar agtype_values, returning -1, 0, or 1.
 *
 * Strings are compared using the default collation.  Used by B-tree
 * operators, where a lexical sort order is generally expected.
 */
int compare_agtype_scalar_values(agtype_value *a, agtype_value *b)
{
    if (a->type == b->type)
    {
        switch (a->type)
        {
        case AGTV_NULL:
            return 0;
        case AGTV_STRING:
        {
            /* varstr_cmp isn't guaranteed to return 1, 0, -1 */
            int result = varstr_cmp(a->val.string.val, a->val.string.len,
                                    b->val.string.val, b->val.string.len,
                                    DEFAULT_COLLATION_OID);
            if (result > 0)
            {
                return 1;
            }
            else if (result < 0)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }
        case AGTV_NUMERIC:
            return DatumGetInt32(DirectFunctionCall2(
                numeric_cmp, PointerGetDatum(a->val.numeric),
                PointerGetDatum(b->val.numeric)));
        case AGTV_BOOL:
            if (a->val.boolean == b->val.boolean)
            {
                return 0;
            }
            else if (a->val.boolean > b->val.boolean)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        case AGTV_INTEGER:
            if (a->val.int_value == b->val.int_value)
            {
                return 0;
            }
            else if (a->val.int_value > b->val.int_value)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        case AGTV_FLOAT:
            return compare_two_floats_orderability(a->val.float_value,
                                                   b->val.float_value);
        case AGTV_VERTEX:
        case AGTV_EDGE:
        {
            agtype_value *a_id, *b_id;
            graphid a_graphid, b_graphid;

            a_id = GET_AGTYPE_VALUE_OBJECT_VALUE(a, "id");
            b_id = GET_AGTYPE_VALUE_OBJECT_VALUE(b, "id");

            a_graphid = a_id->val.int_value;
            b_graphid = b_id->val.int_value;

            if (a_graphid == b_graphid)
            {
                return 0;
            }
            else if (a_graphid > b_graphid)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }
        case AGTV_PATH:
        {
            int i;

            if (a->val.array.num_elems != b->val.array.num_elems)
                return  a->val.array.num_elems > b->val.array.num_elems ? 1 : -1;

            for (i = 0; i < a->val.array.num_elems; i++)
            {
                agtype_value a_elem, b_elem;
                int res;

                a_elem = a->val.array.elems[i];
                b_elem = b->val.array.elems[i];

                res = compare_agtype_scalar_values(&a_elem, &b_elem);

                if (res)
                {
                    return res;
                }
            }

            return 0;
        }
        default:
            ereport(ERROR, (errmsg("invalid agtype scalar type %d for compare",
                                   a->type)));
        }
    }
    /* check for integer compared to float */
    if (a->type == AGTV_INTEGER && b->type == AGTV_FLOAT)
    {
        return compare_two_floats_orderability((float8)a->val.int_value,
                                               b->val.float_value);
    }
    /* check for float compared to integer */
    if (a->type == AGTV_FLOAT && b->type == AGTV_INTEGER)
    {
        return compare_two_floats_orderability(a->val.float_value,
                                               (float8)b->val.int_value);
    }
    /* check for integer or float compared to numeric */
    if (is_numeric_result(a, b))
    {
        Datum numd, lhsd, rhsd;

        lhsd = get_numeric_datum_from_agtype_value(a);
        rhsd = get_numeric_datum_from_agtype_value(b);
        numd = DirectFunctionCall2(numeric_cmp, lhsd, rhsd);

        return DatumGetInt32(numd);
    }

    ereport(ERROR, (errmsg("agtype input scalar type mismatch")));
    return -1;
}

/*
 * Functions for manipulating the resizeable buffer used by convert_agtype and
 * its subroutines.
 */

/*
 * Reserve 'len' bytes, at the end of the buffer, enlarging it if necessary.
 * Returns the offset to the reserved area. The caller is expected to fill
 * the reserved area later with copy_to_buffer().
 */
int reserve_from_buffer(StringInfo buffer, int len)
{
    int offset;

    /* Make more room if needed */
    enlargeStringInfo(buffer, len);

    /* remember current offset */
    offset = buffer->len;

    /* reserve the space */
    buffer->len += len;

    /*
     * Keep a trailing null in place, even though it's not useful for us; it
     * seems best to preserve the invariants of StringInfos.
     */
    buffer->data[buffer->len] = '\0';

    return offset;
}

/*
 * Copy 'len' bytes to a previously reserved area in buffer.
 */
static void copy_to_buffer(StringInfo buffer, int offset, const char *data,
                           int len)
{
    memcpy(buffer->data + offset, data, len);
}

/*
 * A shorthand for reserve_from_buffer + copy_to_buffer.
 */
static void append_to_buffer(StringInfo buffer, const char *data, int len)
{
    int offset;

    offset = reserve_from_buffer(buffer, len);
    copy_to_buffer(buffer, offset, data, len);
}

/*
 * Append padding, so that the length of the StringInfo is int-aligned.
 * Returns the number of padding bytes appended.
 */
short pad_buffer_to_int(StringInfo buffer)
{
    int padlen;
    int p;
    int offset;

    padlen = INTALIGN(buffer->len) - buffer->len;

    offset = reserve_from_buffer(buffer, padlen);

    /* padlen must be small, so this is probably faster than a memset */
    for (p = 0; p < padlen; p++)
        buffer->data[offset + p] = '\0';

    return padlen;
}

/*
 * Given an agtype_value, convert to agtype. The result is palloc'd.
 */
static agtype *convert_to_agtype(agtype_value *val)
{
    StringInfoData buffer;
    agtentry aentry;
    agtype *res;

    /* Should not already have binary representation */
    Assert(val->type != AGTV_BINARY);

    /* Allocate an output buffer. It will be enlarged as needed */
    initStringInfo(&buffer);

    /* Make room for the varlena header */
    reserve_from_buffer(&buffer, VARHDRSZ);

    convert_agtype_value(&buffer, &aentry, val, 0);

    /*
     * Note: the agtentry of the root is discarded. Therefore the root
     * agtype_container struct must contain enough information to tell what
     * kind of value it is.
     */

    res = (agtype *)buffer.data;

    SET_VARSIZE(res, buffer.len);

    return res;
}

/*
 * Subroutine of convert_agtype: serialize a single agtype_value into buffer.
 *
 * The agtentry header for this node is returned in *header.  It is filled in
 * with the length of this value and appropriate type bits.  If we wish to
 * store an end offset rather than a length, it is the caller's responsibility
 * to adjust for that.
 *
 * If the value is an array or an object, this recurses. 'level' is only used
 * for debugging purposes.
 */
static void convert_agtype_value(StringInfo buffer, agtentry *header,
                                 agtype_value *val, int level)
{
    check_stack_depth();

    if (!val)
        return;

    /*
     * An agtype_value passed as val should never have a type of AGTV_BINARY,
     * and neither should any of its sub-components. Those values will be
     * produced by convert_agtype_array and convert_agtype_object, the results
     * of which will not be passed back to this function as an argument.
     */

    if (IS_A_AGTYPE_SCALAR(val))
        convert_agtype_scalar(buffer, header, val);
    else if (val->type == AGTV_ARRAY)
        convert_agtype_array(buffer, header, val, level);
    else if (val->type == AGTV_OBJECT)
        convert_agtype_object(buffer, header, val, level);
    else
        ereport(ERROR,
                (errmsg("unknown agtype type %d to convert", val->type)));
}

static void convert_agtype_array(StringInfo buffer, agtentry *pheader,
                                 agtype_value *val, int level)
{
    int base_offset;
    int agtentry_offset;
    int i;
    int totallen;
    uint32 header;
    int num_elems = val->val.array.num_elems;

    /* Remember where in the buffer this array starts. */
    base_offset = buffer->len;

    /* Align to 4-byte boundary (any padding counts as part of my data) */
    pad_buffer_to_int(buffer);

    /*
     * Construct the header agtentry and store it in the beginning of the
     * variable-length payload.
     */
    header = num_elems | AGT_FARRAY;
    if (val->val.array.raw_scalar)
    {
        Assert(num_elems == 1);
        Assert(level == 0);
        header |= AGT_FSCALAR;
    }

    append_to_buffer(buffer, (char *)&header, sizeof(uint32));

    /* Reserve space for the agtentrys of the elements. */
    agtentry_offset = reserve_from_buffer(buffer,
                                          sizeof(agtentry) * num_elems);

    totallen = 0;
    for (i = 0; i < num_elems; i++)
    {
        agtype_value *elem = &val->val.array.elems[i];
        int len;
        agtentry meta;

        /*
         * Convert element, producing a agtentry and appending its
         * variable-length data to buffer
         */
        convert_agtype_value(buffer, &meta, elem, level + 1);

        len = AGTE_OFFLENFLD(meta);
        totallen += len;

        /*
         * Bail out if total variable-length data exceeds what will fit in a
         * agtentry length field.  We check this in each iteration, not just
         * once at the end, to forestall possible integer overflow.
         */
        if (totallen > AGTENTRY_OFFLENMASK)
        {
            ereport(
                ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                 errmsg(
                     "total size of agtype array elements exceeds the maximum of %u bytes",
                     AGTENTRY_OFFLENMASK)));
        }

        /*
         * Convert each AGT_OFFSET_STRIDE'th length to an offset.
         */
        if ((i % AGT_OFFSET_STRIDE) == 0)
            meta = (meta & AGTENTRY_TYPEMASK) | totallen | AGTENTRY_HAS_OFF;

        copy_to_buffer(buffer, agtentry_offset, (char *)&meta,
                       sizeof(agtentry));
        agtentry_offset += sizeof(agtentry);
    }

    /* Total data size is everything we've appended to buffer */
    totallen = buffer->len - base_offset;

    /* Check length again, since we didn't include the metadata above */
    if (totallen > AGTENTRY_OFFLENMASK)
    {
        ereport(
            ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
             errmsg(
                 "total size of agtype array elements exceeds the maximum of %u bytes",
                 AGTENTRY_OFFLENMASK)));
    }

    /* Initialize the header of this node in the container's agtentry array */
    *pheader = AGTENTRY_IS_CONTAINER | totallen;
}

void convert_extended_array(StringInfo buffer, agtentry *pheader,
                            agtype_value *val)
{
    convert_agtype_array(buffer, pheader, val, 0);
}

void convert_extended_object(StringInfo buffer, agtentry *pheader,
                             agtype_value *val)
{
    convert_agtype_object(buffer, pheader, val, 0);
}

static void convert_agtype_object(StringInfo buffer, agtentry *pheader,
                                  agtype_value *val, int level)
{
    int base_offset;
    int agtentry_offset;
    int i;
    int totallen;
    uint32 header;
    int num_pairs = val->val.object.num_pairs;

    /* Remember where in the buffer this object starts. */
    base_offset = buffer->len;

    /* Align to 4-byte boundary (any padding counts as part of my data) */
    pad_buffer_to_int(buffer);

    /*
     * Construct the header agtentry and store it in the beginning of the
     * variable-length payload.
     */
    header = num_pairs | AGT_FOBJECT;
    append_to_buffer(buffer, (char *)&header, sizeof(uint32));

    /* Reserve space for the agtentrys of the keys and values. */
    agtentry_offset = reserve_from_buffer(buffer,
                                          sizeof(agtentry) * num_pairs * 2);

    /*
     * Iterate over the keys, then over the values, since that is the ordering
     * we want in the on-disk representation.
     */
    totallen = 0;
    for (i = 0; i < num_pairs; i++)
    {
        agtype_pair *pair = &val->val.object.pairs[i];
        int len;
        agtentry meta;

        /*
         * Convert key, producing an agtentry and appending its variable-length
         * data to buffer
         */
        convert_agtype_scalar(buffer, &meta, &pair->key);

        len = AGTE_OFFLENFLD(meta);
        totallen += len;

        /*
         * Bail out if total variable-length data exceeds what will fit in a
         * agtentry length field.  We check this in each iteration, not just
         * once at the end, to forestall possible integer overflow.
         */
        if (totallen > AGTENTRY_OFFLENMASK)
        {
            ereport(
                ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                 errmsg(
                     "total size of agtype object elements exceeds the maximum of %u bytes",
                     AGTENTRY_OFFLENMASK)));
        }

        /*
         * Convert each AGT_OFFSET_STRIDE'th length to an offset.
         */
        if ((i % AGT_OFFSET_STRIDE) == 0)
            meta = (meta & AGTENTRY_TYPEMASK) | totallen | AGTENTRY_HAS_OFF;

        copy_to_buffer(buffer, agtentry_offset, (char *)&meta,
                       sizeof(agtentry));
        agtentry_offset += sizeof(agtentry);
    }
    for (i = 0; i < num_pairs; i++)
    {
        agtype_pair *pair = &val->val.object.pairs[i];
        int len;
        agtentry meta;

        /*
         * Convert value, producing an agtentry and appending its
         * variable-length data to buffer
         */
        convert_agtype_value(buffer, &meta, &pair->value, level + 1);

        len = AGTE_OFFLENFLD(meta);
        totallen += len;

        /*
         * Bail out if total variable-length data exceeds what will fit in a
         * agtentry length field.  We check this in each iteration, not just
         * once at the end, to forestall possible integer overflow.
         */
        if (totallen > AGTENTRY_OFFLENMASK)
        {
            ereport(
                ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                 errmsg(
                     "total size of agtype object elements exceeds the maximum of %u bytes",
                     AGTENTRY_OFFLENMASK)));
        }

        /*
         * Convert each AGT_OFFSET_STRIDE'th length to an offset.
         */
        if (((i + num_pairs) % AGT_OFFSET_STRIDE) == 0)
            meta = (meta & AGTENTRY_TYPEMASK) | totallen | AGTENTRY_HAS_OFF;

        copy_to_buffer(buffer, agtentry_offset, (char *)&meta,
                       sizeof(agtentry));
        agtentry_offset += sizeof(agtentry);
    }

    /* Total data size is everything we've appended to buffer */
    totallen = buffer->len - base_offset;

    /* Check length again, since we didn't include the metadata above */
    if (totallen > AGTENTRY_OFFLENMASK)
    {
        ereport(
            ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
             errmsg(
                 "total size of agtype object elements exceeds the maximum of %u bytes",
                 AGTENTRY_OFFLENMASK)));
    }

    /* Initialize the header of this node in the container's agtentry array */
    *pheader = AGTENTRY_IS_CONTAINER | totallen;
}

static void convert_agtype_scalar(StringInfo buffer, agtentry *entry,
                                  agtype_value *scalar_val)
{
    int numlen;
    short padlen;
    bool status;

    switch (scalar_val->type)
    {
    case AGTV_NULL:
        *entry = AGTENTRY_IS_NULL;
        break;

    case AGTV_STRING:
        append_to_buffer(buffer, scalar_val->val.string.val,
                         scalar_val->val.string.len);

        *entry = scalar_val->val.string.len;
        break;

    case AGTV_NUMERIC:
        numlen = VARSIZE_ANY(scalar_val->val.numeric);
        padlen = pad_buffer_to_int(buffer);

        append_to_buffer(buffer, (char *)scalar_val->val.numeric, numlen);

        *entry = AGTENTRY_IS_NUMERIC | (padlen + numlen);
        break;

    case AGTV_BOOL:
        *entry = (scalar_val->val.boolean) ? AGTENTRY_IS_BOOL_TRUE :
                                             AGTENTRY_IS_BOOL_FALSE;
        break;

    default:
        /* returns true if there was a valid extended type processed */
        status = ag_serialize_extended_type(buffer, entry, scalar_val);
        /* if nothing was found, error log out */
        if (!status)
            ereport(ERROR, (errmsg("invalid agtype scalar type %d to convert",
                                   scalar_val->type)));
    }
}

/*
 * Compare two AGTV_STRING agtype_value values, a and b.
 *
 * This is a special qsort() comparator used to sort strings in certain
 * internal contexts where it is sufficient to have a well-defined sort order.
 * In particular, object pair keys are sorted according to this criteria to
 * facilitate cheap binary searches where we don't care about lexical sort
 * order.
 *
 * a and b are first sorted based on their length.  If a tie-breaker is
 * required, only then do we consider string binary equality.
 */
static int length_compare_agtype_string_value(const void *a, const void *b)
{
    const agtype_value *va = (const agtype_value *)a;
    const agtype_value *vb = (const agtype_value *)b;
    int res;

    Assert(va->type == AGTV_STRING);
    Assert(vb->type == AGTV_STRING);

    if (va->val.string.len == vb->val.string.len)
    {
        res = memcmp(va->val.string.val, vb->val.string.val,
                     va->val.string.len);
    }
    else
    {
        res = (va->val.string.len > vb->val.string.len) ? 1 : -1;
    }

    return res;
}

/*
 * qsort_arg() comparator to compare agtype_pair values.
 *
 * Third argument 'binequal' may point to a bool. If it's set, *binequal is set
 * to true iff a and b have full binary equality, since some callers have an
 * interest in whether the two values are equal or merely equivalent.
 *
 * N.B: String comparisons here are "length-wise"
 *
 * Pairs with equals keys are ordered such that the order field is respected.
 */
static int length_compare_agtype_pair(const void *a, const void *b,
                                      void *binequal)
{
    const agtype_pair *pa = (const agtype_pair *)a;
    const agtype_pair *pb = (const agtype_pair *)b;
    int res;

    res = length_compare_agtype_string_value(&pa->key, &pb->key);
    if (res == 0 && binequal)
        *((bool *)binequal) = true;

    /*
     * Guarantee keeping order of equal pair.  Unique algorithm will prefer
     * first element as value.
     */
    if (res == 0)
        res = (pa->order > pb->order) ? -1 : 1;

    return res;
}

/*
 * Sort and unique-ify pairs in agtype_value object
 */
void uniqueify_agtype_object(agtype_value *object)
{
    bool has_non_uniq = false;

    Assert(object->type == AGTV_OBJECT);

    if (object->val.object.num_pairs > 1)
        qsort_arg(object->val.object.pairs, object->val.object.num_pairs,
                  sizeof(agtype_pair), length_compare_agtype_pair,
                  &has_non_uniq);

    if (has_non_uniq)
    {
        agtype_pair *ptr = object->val.object.pairs + 1;
        agtype_pair *res = object->val.object.pairs;

        while (ptr - object->val.object.pairs < object->val.object.num_pairs)
        {
            /* Avoid copying over duplicate */
            if (length_compare_agtype_string_value(ptr, res) != 0)
            {
                res++;
                if (ptr != res)
                    memcpy(res, ptr, sizeof(agtype_pair));
            }
            ptr++;
        }

        object->val.object.num_pairs = res + 1 - object->val.object.pairs;
    }
}

char *agtype_value_type_to_string(enum agtype_value_type type)
{
    switch (type)
    {
        case AGTV_NULL:
            return "NULL";
        case AGTV_STRING:
            return "string";
        case AGTV_NUMERIC:
            return "numeric";
        case AGTV_INTEGER:
            return "integer";
        case AGTV_FLOAT:
            return "float";
        case AGTV_BOOL:
            return "boolean";
        case AGTV_VERTEX:
            return "vertex";
        case AGTV_EDGE:
            return "edge";
        case AGTV_ARRAY:
            return "array";
        case AGTV_OBJECT:
            return "map";
        case AGTV_BINARY:
            return "binary";
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                            errmsg("unknown agtype")));
    }

    return NULL;
}

/*
 * Deallocates the passed agtype_value recursively.
 */
void pfree_agtype_value(agtype_value* value)
{
    pfree_agtype_value_content(value);
    pfree(value);
}

/*
 * Helper function that recursively deallocates the contents 
 * of the passed agtype_value only. It does not deallocate
 * `value` itself.
 */
void pfree_agtype_value_content(agtype_value* value)
{
    int i;

    // guards against stack overflow due to deeply nested agtype_value
    check_stack_depth();

    switch (value->type)
    {
        case AGTV_NUMERIC:
            pfree(value->val.numeric);
            break;

        case AGTV_STRING:
            /*
             * The char pointer (val.string.val) is not free'd because
             * it is not allocated by an agtype helper function.
             */
            break;

        case AGTV_ARRAY:
        case AGTV_PATH:
            for (i = 0; i < value->val.array.num_elems; i++)
            {
                pfree_agtype_value_content(&value->val.array.elems[i]);
            }
            pfree(value->val.array.elems);
            break;

        case AGTV_OBJECT:
        case AGTV_VERTEX:
        case AGTV_EDGE:
            for (i = 0; i < value->val.object.num_pairs; i++)
            {
                pfree_agtype_value_content(&value->val.object.pairs[i].key);
                pfree_agtype_value_content(&value->val.object.pairs[i].value);
            }
            pfree(value->val.object.pairs);
            break;

        case AGTV_BINARY:
            pfree(value->val.binary.data);
            break;

        case AGTV_NULL:
        case AGTV_INTEGER:
        case AGTV_FLOAT:
        case AGTV_BOOL:
            /*
             * These are deallocated by the calling function.
             */
            break;

        default:
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                     errmsg("unknown agtype")));
            break;
    }
}

void pfree_agtype_in_state(agtype_in_state* value)
{
    pfree_agtype_value(value->res);
    free(value->parse_state);
}

/*
 * helper function that recursively unpacks the agtype_value to be copied
 * and pushes the scalar values into the copied agtype_value.
 * this helps skip the serialization part at some places where the original
 * properties passed to the function are in agtype_value format and
 * converting it to agtype for iteration can be expensive.
 * the caller of this function will need to push start and end object tokens
 * on its own as this function might be used in places where pushing only start
 * object token at top level is required (for example in alter_properties)
 */
void copy_agtype_value(agtype_parse_state* pstate,
                       agtype_value* original_agtype_value,
                       agtype_value **copied_agtype_value, bool is_top_level)
{
    int i = 0;

    /*
     * guards against stack overflow due to deeply nested agtype_value
     */
    check_stack_depth();

    /*
     * directly pass the agtype_value to be pushed into the copied result
     * if type is scalar or binary (array or object) as push_agtype_value
     * can unpack binary on its own
     */
    if (IS_A_AGTYPE_SCALAR(original_agtype_value) ||
        original_agtype_value->type == AGTV_BINARY)
    {
        *copied_agtype_value = push_agtype_value(&pstate, WAGT_ELEM,
                                                 original_agtype_value);
    }
    /*
     * if the passed in type is object or array, unpack it
     * until we are left with a scalar value to push to copied result
     */
    else if (original_agtype_value->type == AGTV_OBJECT)
    {
        if (!is_top_level)
        {
            *copied_agtype_value = push_agtype_value(&pstate,
                                                     WAGT_BEGIN_OBJECT,
                                                     NULL);
        }

        for (; i < original_agtype_value->val.object.num_pairs; i ++)
        {
            agtype_pair *pair = original_agtype_value->val.object.pairs + i;
            *copied_agtype_value = push_agtype_value(&pstate, WAGT_KEY,
                                                     &pair->key);

            if (IS_A_AGTYPE_SCALAR(&pair->value))
            {
                *copied_agtype_value = push_agtype_value(&pstate, WAGT_VALUE,
                                                         &pair->value);
            }
            else
            {
                /* do a recursive call once a non-scalar value is reached */
                copy_agtype_value(pstate, &pair->value, copied_agtype_value,
                                  false);
            }
        }

        if (!is_top_level)
        {
            *copied_agtype_value = push_agtype_value(&pstate, WAGT_END_OBJECT,
                                                     NULL);
        }
    }
    else if (original_agtype_value->type == AGTV_ARRAY)
    {
        *copied_agtype_value = push_agtype_value(&pstate, WAGT_BEGIN_ARRAY,
                                                 NULL);

        for (; i < original_agtype_value->val.array.num_elems; i++)
        {
            agtype_value elem = original_agtype_value->val.array.elems[i];

            if (IS_A_AGTYPE_SCALAR(&elem))
            {
                *copied_agtype_value = push_agtype_value(&pstate, WAGT_ELEM,
                                                         &elem);
            }
            else
            {
                /* do a recursive call once a non-scalar value is reached */
                copy_agtype_value(pstate, &elem, copied_agtype_value, false);
            }
        }

        *copied_agtype_value = push_agtype_value(&pstate, WAGT_END_ARRAY,
                                                 NULL);
    }
    else
    {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("invalid type provided for copy_agtype_value")));
    }
}
