You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jg...@apache.org on 2022/03/01 00:22:23 UTC
[incubator-age] branch master updated: Refactor agtype_access_operator
This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git
The following commit(s) were added to refs/heads/master by this push:
new 466f094 Refactor agtype_access_operator
466f094 is described below
commit 466f09408a3fb5489b882bb380db3e6fe2229c4a
Author: John Gemignani <jr...@gmail.com>
AuthorDate: Thu Feb 24 17:01:34 2022 -0800
Refactor agtype_access_operator
Refactored the agtype_access_operator and support functions to
eliminate the constant conversions back-and-forth.
Also corrected the response to AGTV_NULL
Moved get_agtype_key from age_vle.c to agtype.c for usage by
others.
Adjusted 2 regression tests.
---
regress/expected/expr.out | 4 +-
src/backend/utils/adt/age_vle.c | 38 ----
src/backend/utils/adt/agtype.c | 409 +++++++++++++++++++++++++++++++-----
src/backend/utils/adt/agtype_util.c | 4 +-
src/include/utils/agtype.h | 3 +-
5 files changed, 364 insertions(+), 94 deletions(-)
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index e425f58..f0b2626 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -5404,7 +5404,7 @@ SELECT * FROM cypher('case_statement', $$
$$ ) AS (i agtype, j agtype, case_statement agtype);
i | j | case_statement
------+-----------+----------------
- 1 | null | "i is 1"
+ 1 | | "i is 1"
"a" | "b" | "j is b"
0 | 1 | "0 AND 1"
true | false | "i or j true"
@@ -5427,7 +5427,7 @@ SELECT * FROM cypher('case_statement', $$
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
-----------+----------------
- null | "not a or b"
+ | "not a or b"
"b" | "b"
1 | 1
false | false
diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c
index 9d839dc..42c3092 100644
--- a/src/backend/utils/adt/age_vle.c
+++ b/src/backend/utils/adt/age_vle.c
@@ -101,8 +101,6 @@ typedef struct VLE_path_container
/* declarations */
/* agtype functions */
-static agtype_value *get_agtype_key(agtype_value *agtv, char *search_key,
- int search_key_len);
static agtype_iterator *get_next_object_pair(agtype_iterator *it,
agtype_container *agtc,
agtype_value *key,
@@ -388,42 +386,6 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee)
}
/*
- * Helper function to iterate through all object pairs, looking for a specific
- * key. It will return the key or NULL if not found.
- */
-static agtype_value *get_agtype_key(agtype_value *agtv, char *search_key,
- int search_key_len)
-{
- int i = 0;
-
- if (agtv == NULL || search_key == NULL || search_key_len <= 0)
- {
- return NULL;
- }
-
- /* iterate through all pairs */
- for (i = 0; i < agtv->val.object.num_pairs; i++)
- {
- agtype_value *agtv_key = &agtv->val.object.pairs[i].key;
- agtype_value *agtv_value = &agtv->val.object.pairs[i].value;
-
- char *current_key = agtv_key->val.string.val;
- int current_key_len = agtv_key->val.string.len;
-
- Assert(agtv_key->type == AGTV_STRING);
-
- /* check for an id of type integer */
- if (current_key_len == search_key_len &&
- pg_strcasecmp(current_key, search_key) == 0)
- {
- return agtv_value;
- }
- }
-
- return NULL;
-}
-
-/*
* Helper function to free up the memory used by the VLE_local_context.
*
* Currently, the only structures that needs to be freed are the edge state
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index 43d24a9..90d5b71 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -126,8 +126,12 @@ static void add_indent(StringInfo out, bool indent, int level);
static void cannot_cast_agtype_value(enum agtype_value_type type,
const char *sqltype);
static bool agtype_extract_scalar(agtype_container *agtc, agtype_value *res);
-static agtype *execute_array_access_operator(agtype *array, agtype *element);
-static agtype *execute_map_access_operator(agtype *map, agtype *key);
+static agtype_value *execute_array_access_operator(agtype *array,
+ agtype_value *array_value,
+ agtype *array_index);
+static agtype_value *execute_map_access_operator(agtype *map,
+ agtype_value* map_value,
+ agtype *key);
/* typecast functions */
static void agtype_typecast_object(agtype_in_state *state, char *annotation);
static void agtype_typecast_array(agtype_in_state *state, char *annotation);
@@ -157,6 +161,10 @@ static agtype_iterator *get_next_object_key(agtype_iterator *it,
static agtype_iterator *get_next_list_element(agtype_iterator *it,
agtype_container *agtc,
agtype_value *elem);
+static int extract_variadic_args_min(FunctionCallInfo fcinfo,
+ int variadic_start, bool convert_unknown,
+ Datum **args, Oid **types, bool **nulls,
+ int min_num_args);
/* fast helper function to test for AGTV_NULL in an agtype */
bool is_agtype_null(agtype *agt_arg)
@@ -2712,15 +2720,19 @@ Datum agtype_to_int4_array(PG_FUNCTION_ARGS)
* Helper function for agtype_access_operator map access.
* Note: This function expects that a map and a scalar key are being passed.
*/
-static agtype *execute_map_access_operator(agtype *map, agtype *key)
+static agtype_value *execute_map_access_operator(agtype *map,
+ agtype_value *map_value,
+ agtype *key)
{
agtype_value *key_value;
- agtype_value *map_value;
agtype_value new_key_value;
+ /* get the key from the container */
key_value = get_ith_agtype_value_from_container(&key->root, 0);
+
/* transform key where appropriate */
new_key_value.type = AGTV_STRING;
+
switch (key_value->type)
{
case AGTV_NULL:
@@ -2738,10 +2750,8 @@ static agtype *execute_map_access_operator(agtype *map, agtype *key)
case AGTV_BOOL:
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("AGTV_BOOL is not a valid key type")));
-
case AGTV_STRING:
-
- new_key_value.val.string = key_value->val.string;
+ new_key_value.val.string.val = key_value->val.string.val;
new_key_value.val.string.len = key_value->val.string.len;
break;
@@ -2750,48 +2760,272 @@ static agtype *execute_map_access_operator(agtype *map, agtype *key)
break;
}
- map_value = find_agtype_value_from_container(&map->root, AGT_FOBJECT,
- &new_key_value);
+ /* if we were passed an agtype */
if (map_value == NULL)
- return NULL;
+ {
+ map_value = find_agtype_value_from_container(&map->root, AGT_FOBJECT,
+ &new_key_value);
+ }
+ /* if we were passed an agtype_value OBJECT (BINARY) */
+ else if (map_value != NULL && map_value->type == AGTV_BINARY)
+ {
+ map_value = find_agtype_value_from_container(map_value->val.binary.data,
+ AGT_FOBJECT,
+ &new_key_value);
+ }
+ /* if we were passed an agtype_value OBJECT */
+ else if (map_value != NULL && map_value->type == AGTV_OBJECT)
+ {
+ map_value = get_agtype_key(map_value, key_value->val.string.val,
+ key_value->val.string.len);
+ }
+ /* otherwise, we don't know how to process it */
+ else
+ {
+ ereport(ERROR, (errmsg("unknown map_value type")));
+ }
- return agtype_value_to_agtype(map_value);
+ /* return the agtype_value */
+ return map_value;
}
/*
* Helper function for agtype_access_operator array access.
- * Note: This function expects that an array and a scalar key are being passed.
+ * Note: This function expects that an array and a scalar int are being passed.
*/
-static agtype *execute_array_access_operator(agtype *array, agtype *element)
+static agtype_value *execute_array_access_operator(agtype *array,
+ agtype_value *array_value,
+ agtype *array_index)
{
- agtype_value *array_value;
- agtype_value *element_value;
- int64 index;
- uint32 size;
+ agtype_value *array_element_value = NULL;
+ agtype_value *array_index_value = NULL;
+ int64 index = 0;
+ uint32 size = 0;
+
+ /* unpack the array index value */
+ array_index_value = get_ith_agtype_value_from_container(&array_index->root,
+ 0);
- element_value = get_ith_agtype_value_from_container(&element->root, 0);
/* if AGTV_NULL return NULL */
- if (element_value->type == AGTV_NULL)
+ if (array_index_value->type == AGTV_NULL)
+ {
return NULL;
- /* key must be an integer */
- if (element_value->type != AGTV_INTEGER)
+ }
+
+ /* index must be an integer */
+ if (array_index_value->type != AGTV_INTEGER)
+ {
ereport(ERROR,
(errmsg("array index must resolve to an integer value")));
+ }
+
/* adjust for negative index values */
- index = element_value->val.int_value;
- size = AGT_ROOT_COUNT(array);
+ index = array_index_value->val.int_value;
+ size = (array_value == NULL) ? AGT_ROOT_COUNT(array) :
+ array_value->val.array.num_elems;
if (index < 0)
+ {
index = size + index;
+ }
+
/* check array bounds */
if ((index >= size) || (index < 0))
+ {
return NULL;
+ }
- array_value = get_ith_agtype_value_from_container(&array->root, index);
-
+ /* if we were passed an agtype */
if (array_value == NULL)
+ {
+ array_element_value = get_ith_agtype_value_from_container(&array->root,
+ index);
+ }
+ /* if we were passed an agtype_value ARRAY (BINARY) */
+ else if (array_value != NULL && array_value->type == AGTV_BINARY)
+ {
+ array_element_value = get_ith_agtype_value_from_container(array_value->val.binary.data,
+ index);
+ }
+ /* if we were passed an agtype_value ARRAY */
+ else if (array_value != NULL && array_value->type == AGTV_ARRAY)
+ {
+ array_element_value = &array_value->val.array.elems[index];
+ }
+ /* otherwise, we don't know how to process it */
+ else
+ {
+ ereport(ERROR, (errmsg("unknown array_value type")));
+ }
+
+ return array_element_value;
+}
+
+/*
+ * Helper function to iterate through all object pairs, looking for a specific
+ * key. It will return the key or NULL if not found.
+ */
+agtype_value *get_agtype_key(agtype_value *agtv, char *search_key,
+ int search_key_len)
+{
+ int i = 0;
+
+ if (agtv == NULL || search_key == NULL || search_key_len <= 0)
+ {
return NULL;
+ }
+
+ /* iterate through all pairs */
+ for (i = 0; i < agtv->val.object.num_pairs; i++)
+ {
+ agtype_value *agtv_key = &agtv->val.object.pairs[i].key;
+ agtype_value *agtv_value = &agtv->val.object.pairs[i].value;
+
+ char *current_key = agtv_key->val.string.val;
+ int current_key_len = agtv_key->val.string.len;
+
+ Assert(agtv_key->type == AGTV_STRING);
- return agtype_value_to_agtype(array_value);
+ /* check for an id of type integer */
+ if (current_key_len == search_key_len &&
+ pg_strcasecmp(current_key, search_key) == 0)
+ {
+ return agtv_value;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * From PG's extract_variadic_args
+ *
+ * This function allows you to pass the minimum number of required arguments
+ * so that you can have it bail out early (without allocating and building
+ * the output arrays). In this case, the returned number of args will be 0
+ * and the args array will be NULL.
+ */
+static int extract_variadic_args_min(FunctionCallInfo fcinfo,
+ int variadic_start, bool convert_unknown,
+ Datum **args, Oid **types, bool **nulls,
+ int min_num_args)
+{
+ bool variadic = get_fn_expr_variadic(fcinfo->flinfo);
+ Datum *args_res = NULL;
+ bool *nulls_res = NULL;
+ Oid *types_res = NULL;
+ int nargs = 0;
+ int i = 0;
+
+ *args = NULL;
+ *types = NULL;
+ *nulls = NULL;
+
+ if (variadic)
+ {
+ ArrayType *array_in = NULL;
+ Oid element_type = InvalidOid;
+ bool typbyval = false;
+ char typalign = 0;
+ int16 typlen = 0;
+
+ Assert(PG_NARGS() == variadic_start + 1);
+
+ if (PG_ARGISNULL(variadic_start))
+ {
+ return -1;
+ }
+
+ /* get the array */
+ array_in = PG_GETARG_ARRAYTYPE_P(variadic_start);
+
+ /* verify that we have the minimum number of args */
+ if (ArrayGetNItems(ARR_NDIM(array_in), ARR_DIMS(array_in)) <
+ min_num_args)
+ {
+ return 0;
+ }
+
+ /* get the element type */
+ element_type = ARR_ELEMTYPE(array_in);
+
+ get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);
+ deconstruct_array(array_in, element_type, typlen, typbyval, typalign,
+ &args_res, &nulls_res, &nargs);
+
+ /* All the elements of the array have the same type */
+ types_res = (Oid *) palloc0(nargs * sizeof(Oid));
+ for (i = 0; i < nargs; i++)
+ {
+ types_res[i] = element_type;
+ }
+ }
+ else
+ {
+ /* get the number of arguments */
+ nargs = PG_NARGS() - variadic_start;
+ Assert(nargs > 0);
+
+ /* verify that we have the minimum number of args */
+ if (nargs < min_num_args)
+ {
+ return 0;
+ }
+
+ /* allocate result memory */
+ nulls_res = (bool *) palloc0(nargs * sizeof(bool));
+ args_res = (Datum *) palloc0(nargs * sizeof(Datum));
+ types_res = (Oid *) palloc0(nargs * sizeof(Oid));
+
+ for (i = 0; i < nargs; i++)
+ {
+ nulls_res[i] = PG_ARGISNULL(i + variadic_start);
+ types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, i + variadic_start);
+
+ /*
+ * Turn a constant (more or less literal) value that's of unknown
+ * type into text if required. Unknowns come in as a cstring
+ * pointer. Note: for functions declared as taking type "any", the
+ * parser will not do any type conversion on unknown-type literals
+ * (that is, undecorated strings or NULLs).
+ */
+ if (convert_unknown &&
+ types_res[i] == UNKNOWNOID &&
+ get_fn_expr_arg_stable(fcinfo->flinfo, i + variadic_start))
+ {
+ types_res[i] = TEXTOID;
+
+ if (PG_ARGISNULL(i + variadic_start))
+ {
+ args_res[i] = (Datum) 0;
+ }
+ else
+ {
+ args_res[i] = CStringGetTextDatum(PG_GETARG_POINTER(i + variadic_start));
+ }
+ }
+ else
+ {
+ /* no conversion needed, just take the datum as given */
+ args_res[i] = PG_GETARG_DATUM(i + variadic_start);
+ }
+
+ if (!OidIsValid(types_res[i]) ||
+ (convert_unknown && types_res[i] == UNKNOWNOID))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine data type for argument %d",
+ i + 1)));
+ }
+ }
+ }
+
+ /* Fill in results */
+ *args = args_res;
+ *nulls = nulls_res;
+ *types = types_res;
+
+ return nargs;
}
PG_FUNCTION_INFO_V1(agtype_access_operator);
@@ -2801,62 +3035,133 @@ PG_FUNCTION_INFO_V1(agtype_access_operator);
*/
Datum agtype_access_operator(PG_FUNCTION_ARGS)
{
- int nargs;
- Datum *args;
- bool *nulls;
- Oid *types;
- agtype *object;
- agtype *key;
- int i;
+ Datum *args = NULL;
+ bool *nulls = NULL;
+ Oid *types = NULL;
+ int nargs = 0;
+ agtype *object = NULL;
+ agtype_value *object_value = NULL;
+ int i = 0;
- nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
- /* we need at least 2 parameters, the object, and a field or element */
- if (nargs < 2)
+ /* extract our args, we need at least 2 */
+ nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, &nulls,
+ 2);
+ /* return NULL if we don't have the minimum number of args */
+ if (args == NULL && nargs == 0)
+ {
PG_RETURN_NULL();
+ }
+ /* get the object argument */
object = DATUM_GET_AGTYPE_P(args[0]);
+
+ /* if the object is a scalar, it must be a vertex or edge */
if (AGT_ROOT_IS_SCALAR(object))
{
- agtype_value *v;
- v = get_ith_agtype_value_from_container(&object->root, 0);
+ agtype_value *scalar_value = NULL;
+ agtype_value *property_value = NULL;
+
+ /* unpack the scalar */
+ scalar_value = get_ith_agtype_value_from_container(&object->root, 0);
- if (v->type == AGTV_VERTEX)
- object = agtype_value_to_agtype(&v->val.object.pairs[2].value);
- else if (v->type == AGTV_EDGE)
- object = agtype_value_to_agtype(&v->val.object.pairs[4].value);
+ /* get the properties depending on the type or fail */
+ if (scalar_value->type == AGTV_VERTEX)
+ {
+ property_value = &scalar_value->val.object.pairs[2].value;
+ }
+ else if (scalar_value->type == AGTV_EDGE)
+ {
+ property_value = &scalar_value->val.object.pairs[4].value;
+ }
else
- ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("container must be an array or object")));
+ {
+ ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("scalar object must be a vertex or edge")));
+ }
- //object = agtype_value_to_agtype(&v->val.object.pairs[2].value);
+ /* if the properties are NULL, return NULL */
+ if (property_value == NULL || property_value->type == AGTV_NULL)
+ {
+ PG_RETURN_NULL();
+ }
+
+ /* set the object_value to the property_value. */
+ object_value = property_value;
}
+ /* check for NULL keys */
for (i = 1; i < nargs; i++)
{
- /* if we have a null, return null */
+ /* if we have a NULL, return NULL */
if (nulls[i] == true)
+ {
PG_RETURN_NULL();
+ }
+ }
+
+ /* iterate through the keys */
+ for (i = 1; i < nargs; i++)
+ {
+ agtype *key = NULL;
+ /* get the key */
key = DATUM_GET_AGTYPE_P(args[i]);
+
+ /* the key must be a scalar */
if (!(AGT_ROOT_IS_SCALAR(key)))
{
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("key must resolve to a scalar value")));
}
- if (AGT_ROOT_IS_OBJECT(object))
- object = execute_map_access_operator(object, key);
- else if (AGT_ROOT_IS_ARRAY(object))
- object = execute_array_access_operator(object, key);
+ /*
+ * If we are dealing with a type of object, which can be an -
+ * agtype OBJECT, an agtype_value OBJECT serialized (BINARY), or an
+ * agtype_value OBJECT deserialized.
+ */
+ if ((object_value != NULL &&
+ (object_value->type == AGTV_OBJECT ||
+ (object_value->type == AGTV_BINARY &&
+ AGTYPE_CONTAINER_IS_OBJECT(object_value->val.binary.data)))) ||
+ (object != NULL && AGT_ROOT_IS_OBJECT(object)))
+ {
+ object_value = execute_map_access_operator(object, object_value,
+ key);
+ }
+ /*
+ * If we are dealing with a type of array, which can be an -
+ * agtype ARRAY, an agtype_value ARRAY serialized (BINARY), or an
+ * agtype_value ARRAY deserialized.
+ */
+ else if ((object_value != NULL &&
+ (object_value->type == AGTV_ARRAY ||
+ (object_value->type == AGTV_BINARY &&
+ AGTYPE_CONTAINER_IS_ARRAY(object_value->val.binary.data)))) ||
+ (object != NULL && AGT_ROOT_IS_ARRAY(object)))
+ {
+ object_value = execute_array_access_operator(object, object_value,
+ key);
+ }
+ /* this is unexpected */
else
+ {
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("container must be an array or object")));
+ }
- if (object == NULL)
+ /* for NULL values return NULL */
+ if (object_value == NULL || object_value->type == AGTV_NULL)
+ {
PG_RETURN_NULL();
+ }
+
+ /* clear the object reference */
+ object = NULL;
+
}
- return AGTYPE_P_GET_DATUM(object);
+ /* serialize and return the result */
+ return AGTYPE_P_GET_DATUM(agtype_value_to_agtype(object_value));
}
PG_FUNCTION_INFO_V1(agtype_access_slice);
diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c
index 0401ec0..104d0c3 100644
--- a/src/backend/utils/adt/agtype_util.c
+++ b/src/backend/utils/adt/agtype_util.c
@@ -422,7 +422,9 @@ agtype_value *find_agtype_value_from_container(agtype_container *container,
/* Quick out without a palloc cycle if object/array is empty */
if (count <= 0)
+ {
return NULL;
+ }
result = palloc(sizeof(agtype_value));
@@ -2228,4 +2230,4 @@ char *agtype_value_type_to_string(enum agtype_value_type type)
}
return NULL;
-}
\ No newline at end of file
+}
diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h
index bbc969e..c1780ed 100644
--- a/src/include/utils/agtype.h
+++ b/src/include/utils/agtype.h
@@ -526,7 +526,8 @@ agtype_value *string_to_agtype_value(char *s);
agtype_value *integer_to_agtype_value(int64 int_value);
void add_agtype(Datum val, bool is_null, agtype_in_state *result, Oid val_type,
bool key_scalar);
-
+agtype_value *get_agtype_key(agtype_value *agtv, char *search_key,
+ int search_key_len);
// OID of agtype and _agtype
#define AGTYPEOID \
(GetSysCacheOid2(TYPENAMENSP, CStringGetDatum("agtype"), \