/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Functions for operators in Cypher expressions. */ #include "postgres.h" #include #include #include "utils/agtype.h" #include "utils/builtins.h" #include "pg_fix.h" static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2); static agtype_value *iterator_concat(agtype_iterator **it1, agtype_iterator **it2, agtype_parse_state **state); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen); static char *get_string_from_agtype_value(agtype_value *agtv, int *length); static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text); static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen); static agtype *delete_from_array(agtype *agt, agtype* indexes); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen) { int length = llen + rlen; char *buffer = result->val.string.val; Assert(llen >= 0 && rlen >= 0); check_string_length(length); buffer = palloc(length); strncpy(buffer, lhs, llen); strncpy(buffer + llen, rhs, rlen); result->type = AGTV_STRING; result->val.string.len = length; result->val.string.val = buffer; } static char *get_string_from_agtype_value(agtype_value *agtv, int *length) { Datum number; char *string; switch (agtv->type) { case AGTV_INTEGER: number = DirectFunctionCall1(int8out, Int8GetDatum(agtv->val.int_value)); string = DatumGetCString(number); *length = strlen(string); return string; case AGTV_FLOAT: number = DirectFunctionCall1(float8out, Float8GetDatum(agtv->val.float_value)); string = DatumGetCString(number); *length = strlen(string); if (is_decimal_needed(string)) { char *str = palloc(*length + 2); strncpy(str, string, *length); strncpy(str + *length, ".0", 2); *length += 2; string = str; } return string; case AGTV_STRING: *length = agtv->val.string.len; return agtv->val.string.val; case AGTV_NUMERIC: string = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(agtv->val.numeric))); *length = strlen(string); return string; case AGTV_NULL: case AGTV_BOOL: case AGTV_ARRAY: case AGTV_OBJECT: case AGTV_BINARY: default: *length = 0; return NULL; } return NULL; } Datum get_numeric_datum_from_agtype_value(agtype_value *agtv) { switch (agtv->type) { case AGTV_INTEGER: return DirectFunctionCall1(int8_numeric, Int8GetDatum(agtv->val.int_value)); case AGTV_FLOAT: return DirectFunctionCall1(float8_numeric, Float8GetDatum(agtv->val.float_value)); case AGTV_NUMERIC: return NumericGetDatum(agtv->val.numeric); default: break; } return 0; } bool is_numeric_result(agtype_value *lhs, agtype_value *rhs) { if (((lhs->type == AGTV_NUMERIC || rhs->type == AGTV_NUMERIC) && (lhs->type == AGTV_INTEGER || lhs->type == AGTV_FLOAT || rhs->type == AGTV_INTEGER || rhs->type == AGTV_FLOAT )) || (lhs->type == AGTV_NUMERIC && rhs->type == AGTV_NUMERIC)) { return true; } return false; } PG_FUNCTION_INFO_V1(agtype_add); /* agtype addition and concat function for + operator */ Datum agtype_add(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; /* If both are not scalars */ if (!(AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs))) { Datum agt = AGTYPE_P_GET_DATUM(agtype_concat_impl(lhs, rhs)); PG_RETURN_DATUM(agt); } /* Both are scalar */ agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); /* * One or both values is a string OR one is a string and the other is * either an integer, float, or numeric. If so, concatenate them. */ if ((agtv_lhs->type == AGTV_STRING || agtv_rhs->type == AGTV_STRING) && (agtv_lhs->type == AGTV_INTEGER || agtv_lhs->type == AGTV_FLOAT || agtv_lhs->type == AGTV_NUMERIC || agtv_lhs->type == AGTV_STRING || agtv_rhs->type == AGTV_INTEGER || agtv_rhs->type == AGTV_FLOAT || agtv_rhs->type == AGTV_NUMERIC || agtv_rhs->type == AGTV_STRING)) { int llen = 0; char *lhs = get_string_from_agtype_value(agtv_lhs, &llen); int rlen = 0; char *rhs = get_string_from_agtype_value(agtv_rhs, &rlen); concat_to_agtype_string(&agtv_result, lhs, llen, rhs, rlen); } /* Both are integers - regular addition */ else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = agtv_lhs->val.int_value + agtv_rhs->val.int_value; } /* Both are floats - regular addition */ else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value + agtv_rhs->val.float_value; } /* The left is a float, the right is an integer - regular addition */ else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value + agtv_rhs->val.int_value; } /* The right is a float, the left is an integer - regular addition */ else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.int_value + agtv_rhs->val.float_value; } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_add, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } /* if both operands are scalar(vertex/edge/path), concat the two */ else if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { Datum agt = AGTYPE_P_GET_DATUM(agtype_concat_impl(lhs, rhs)); PG_RETURN_DATUM(agt); } else { /* Not a covered case, error out */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_add"))); } AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_add); /* agtype addition between bigint and agtype */ Datum agtype_any_add(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_add, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); AG_RETURN_AGTYPE_P(DATUM_GET_AGTYPE_P(result)); } /* * For the given indexes array, delete elements at those indexes * from the passed in agtype array. */ static agtype *delete_from_array(agtype *agt, agtype *indexes) { agtype_parse_state *state = NULL; agtype_iterator *it, *it_indexes = NULL; uint32 i = 0, n; agtype_value v, *res = NULL; agtype_iterator_token r; if (!AGT_ROOT_IS_ARRAY(agt) || AGT_ROOT_IS_SCALAR(agt)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar or object" "using integer index"))); } // array is empty, pass the original array if (AGT_ROOT_COUNT(agt) == 0) { return agt; } // start buidiling the result agtype array it = agtype_iterator_init(&agt->root); r = agtype_iterator_next(&it, &v, false); Assert(r == WAGT_BEGIN_ARRAY); n = v.val.array.num_elems; push_agtype_value(&state, r, NULL); while ((r = agtype_iterator_next(&it, &v, true)) != WAGT_DONE) { if (r == WAGT_ELEM) { /* * use logic similar to agtype_contains to check * if the current index (itself or in inverted form) * is contained in the indexes array, * if yes, skip the element at that index in agt array * else add the element in result agtype array */ agtype_value cur_idx, neg_idx; agtype *cur_idx_agt, *neg_idx_agt; agtype_iterator *it_cur_idx, *it_neg_idx; bool contains_idx, contains_neg_idx; cur_idx.type = AGTV_INTEGER; cur_idx.val.int_value = i++; cur_idx_agt = agtype_value_to_agtype(&cur_idx); neg_idx.type = AGTV_INTEGER; neg_idx.val.int_value = cur_idx.val.int_value - n; neg_idx_agt = agtype_value_to_agtype(&neg_idx); it_cur_idx = agtype_iterator_init(&cur_idx_agt->root); it_neg_idx = agtype_iterator_init(&neg_idx_agt->root); it_indexes = agtype_iterator_init(&indexes->root); contains_idx = agtype_deep_contains(&it_indexes, &it_cur_idx); // re-initialize indexes array iterator it_indexes = agtype_iterator_init(&indexes->root); contains_neg_idx = agtype_deep_contains(&it_indexes, &it_neg_idx); if (contains_idx || contains_neg_idx) { continue; } } res = push_agtype_value(&state, r, r < WAGT_BEGIN_ARRAY ? &v : NULL); } Assert(res != NULL); return agtype_value_to_agtype(res); } /* * For the given key delete that property from the passed in agtype * object. */ static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen) { agtype_parse_state *state = NULL; agtype_iterator *it; agtype_value v, *res = NULL; bool skipNested = false; agtype_iterator_token r; if (!AGT_ROOT_IS_OBJECT(agt)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar or array" "using string key"))); } if (AGT_ROOT_COUNT(agt) == 0) { return agt; } it = agtype_iterator_init(&agt->root); while ((r = agtype_iterator_next(&it, &v, skipNested)) != WAGT_DONE) { skipNested = true; /* * Checks the key to compare against the passed in key to be * deleted. do not add the key and value to the new agtype being * constructed. */ if ((r == WAGT_ELEM || r == WAGT_KEY) && (v.type == AGTV_STRING && keylen == v.val.string.len && memcmp(keyptr, v.val.string.val, keylen) == 0)) { /* skip corresponding value as well */ if (r == WAGT_KEY) { (void) agtype_iterator_next(&it, &v, true); } continue; } res = push_agtype_value(&state, r, r < WAGT_BEGIN_ARRAY ? &v : NULL); } Assert(res != NULL); return agtype_value_to_agtype(res); } PG_FUNCTION_INFO_V1(agtype_sub); /* * agtype subtraction function for - operator */ Datum agtype_sub(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; /* * Logic to handle when the rhs is a non scalar array. In this * case; * 1. if the lhs is an object, the values in the rhs array * are string keys to be removed from the object. * 2. if the lhs is an array, the values in the rhs array * are integer indexes at which values should be removed from array. * otherwise throw an error */ if (AGT_ROOT_IS_ARRAY(rhs) && !AGT_ROOT_IS_SCALAR(rhs)) { agtype_iterator *it = NULL; agtype_value elem; if (AGT_ROOT_IS_OBJECT(lhs)) { /* * if rhs array contains any non-string element, error out * else delete the given keys in the rhs array from lhs object */ while ((it = get_next_list_element(it, &rhs->root, &elem))) { if (elem.type == AGTV_STRING) { lhs = delete_from_object(lhs, elem.val.string.val, elem.val.string.len); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype string, not agtype %s", agtype_value_type_to_string(elem.type)))); } } } else if (AGT_ROOT_IS_ARRAY(lhs) && !(AGT_ROOT_IS_SCALAR(lhs))) { /* * if rhs array contains any non-integer element, error out * else delete the values at the given indexes in rhs array * from the lhs array */ while ((it = get_next_list_element(it, &rhs->root, &elem))) { if (elem.type != AGTV_INTEGER) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype integer, not agtype %s", agtype_value_type_to_string(elem.type)))); } } lhs = delete_from_array(lhs, rhs); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be object or array, not a scalar value"))); } AG_RETURN_AGTYPE_P(lhs); } /* * When the lhs is an object and rhs is a string, remove the key from * the object. * When the lhs is an array and the rhs is an integer then * remove the value at that index from the array, * otherwise give an error */ if(!AGT_ROOT_IS_SCALAR(lhs)) { agtype_value *key; key = get_ith_agtype_value_from_container(&rhs->root, 0); if (AGT_ROOT_IS_OBJECT(lhs) && key->type == AGTV_STRING) { AG_RETURN_AGTYPE_P(delete_from_object(lhs, key->val.string.val, key->val.string.len)); } else if (AGT_ROOT_IS_ARRAY(lhs) && key->type == AGTV_INTEGER) { AG_RETURN_AGTYPE_P(delete_from_array(lhs, rhs)); } else { if (AGT_ROOT_IS_OBJECT(lhs)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype string, not agtype %s", agtype_value_type_to_string(key->type)))); } else if (AGT_ROOT_IS_ARRAY(lhs)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype integer, not agtype %s", agtype_value_type_to_string(key->type)))); } } } agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = agtv_lhs->val.int_value - agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value - agtv_rhs->val.float_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value - agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.int_value - agtv_rhs->val.float_value; } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_sub, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_sub"))); } AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_sub); /* agtype subtraction between bigint and agtype */ Datum agtype_any_sub(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_sub, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); AG_RETURN_AGTYPE_P(DATUM_GET_AGTYPE_P(result)); } PG_FUNCTION_INFO_V1(agtype_neg); /* * agtype negation function for unary - operator */ Datum agtype_neg(PG_FUNCTION_ARGS) { agtype *v = AG_GET_ARG_AGTYPE_P(0); agtype_value *agtv_value; agtype_value agtv_result; if (!(AGT_ROOT_IS_SCALAR(v))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be scalar value, not array or object"))); PG_RETURN_NULL(); } agtv_value = get_ith_agtype_value_from_container(&v->root, 0); if (agtv_value->type == AGTV_INTEGER) { agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = -agtv_value->val.int_value; } else if (agtv_value->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = -agtv_value->val.float_value; } else if (agtv_value->type == AGTV_NUMERIC) { Datum numd, vald; vald = NumericGetDatum(agtv_value->val.numeric); numd = DirectFunctionCall1(numeric_uminus, vald); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter type for agtype_neg"))); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_mul); /* * agtype multiplication function for * operator */ Datum agtype_mul(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be scalar value, not array or object"))); PG_RETURN_NULL(); } agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = agtv_lhs->val.int_value * agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value * agtv_rhs->val.float_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value * agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.int_value * agtv_rhs->val.float_value; } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_mul, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mul"))); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_mul); /* agtype multiplication between bigint and agtype */ Datum agtype_any_mul(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_mul, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); AG_RETURN_AGTYPE_P(DATUM_GET_AGTYPE_P(result)); } PG_FUNCTION_INFO_V1(agtype_div); /* * agtype division function for / operator */ Datum agtype_div(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be scalar value, not array or object"))); PG_RETURN_NULL(); } agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { if (agtv_rhs->val.int_value == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); PG_RETURN_NULL(); } agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = agtv_lhs->val.int_value / agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { if (agtv_rhs->val.float_value == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); PG_RETURN_NULL(); } agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value / agtv_rhs->val.float_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { if (agtv_rhs->val.int_value == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); PG_RETURN_NULL(); } agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.float_value / agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { if (agtv_rhs->val.float_value == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); PG_RETURN_NULL(); } agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = agtv_lhs->val.int_value / agtv_rhs->val.float_value; } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_div, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_div"))); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_div); /* agtype division between bigint and agtype */ Datum agtype_any_div(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_div, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); AG_RETURN_AGTYPE_P(DATUM_GET_AGTYPE_P(result)); } PG_FUNCTION_INFO_V1(agtype_mod); /* * agtype modulo function for % operator */ Datum agtype_mod(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be scalar value, not array or object"))); PG_RETURN_NULL(); } agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_INTEGER; agtv_result.val.int_value = agtv_lhs->val.int_value % agtv_rhs->val.int_value; } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.float_value, agtv_rhs->val.float_value); } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.float_value, agtv_rhs->val.int_value); } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.int_value, agtv_rhs->val.float_value); } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_mod, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mod"))); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_mod); /* agtype modulo between bigint and agtype */ Datum agtype_any_mod(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_mod, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); AG_RETURN_AGTYPE_P(DATUM_GET_AGTYPE_P(result)); } PG_FUNCTION_INFO_V1(agtype_pow); /* * agtype power function for ^ operator */ Datum agtype_pow(PG_FUNCTION_ARGS) { agtype *lhs = AG_GET_ARG_AGTYPE_P(0); agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; agtype_value agtv_result; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must be scalar value, not array or object"))); PG_RETURN_NULL(); } agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = pow(agtv_lhs->val.int_value, agtv_rhs->val.int_value); } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = pow(agtv_lhs->val.float_value, agtv_rhs->val.float_value); } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = pow(agtv_lhs->val.float_value, agtv_rhs->val.int_value); } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = pow(agtv_lhs->val.int_value, agtv_rhs->val.float_value); } /* Is this a numeric result */ else if (is_numeric_result(agtv_lhs, agtv_rhs)) { Datum numd, lhsd, rhsd; lhsd = get_numeric_datum_from_agtype_value(agtv_lhs); rhsd = get_numeric_datum_from_agtype_value(agtv_rhs); numd = DirectFunctionCall2(numeric_power, lhsd, rhsd); agtv_result.type = AGTV_NUMERIC; agtv_result.val.numeric = DatumGetNumeric(numd); } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_pow"))); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_eq); Datum agtype_eq(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) == 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_eq); Datum agtype_any_eq(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_eq, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_ne); Datum agtype_ne(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result = true; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) != 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_ne); Datum agtype_any_ne(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_ne, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_lt); Datum agtype_lt(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) < 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_lt); Datum agtype_any_lt(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_lt, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_gt); Datum agtype_gt(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) > 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_gt); Datum agtype_any_gt(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_gt, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_le); Datum agtype_le(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) <= 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_le); Datum agtype_any_le(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_le, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_ge); Datum agtype_ge(PG_FUNCTION_ARGS) { agtype *agtype_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agtype_rhs = AG_GET_ARG_AGTYPE_P(1); bool result; result = (compare_agtype_containers_orderability(&agtype_lhs->root, &agtype_rhs->root) >= 0); PG_FREE_IF_COPY(agtype_lhs, 0); PG_FREE_IF_COPY(agtype_rhs, 1); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_any_ge); Datum agtype_any_ge(PG_FUNCTION_ARGS) { agtype *lhs; agtype *rhs; Datum result; lhs = get_one_agtype_from_variadic_args(fcinfo, 0, 2); rhs = get_one_agtype_from_variadic_args(fcinfo, 1, 1); if (lhs == NULL || rhs == NULL) { PG_RETURN_NULL(); } result = DirectFunctionCall2(agtype_ge, AGTYPE_P_GET_DATUM(lhs), AGTYPE_P_GET_DATUM(rhs)); PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_exists_agtype); /* * ? operator for agtype. Returns true if the string exists as top-level keys */ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *key = AG_GET_ARG_AGTYPE_P(1); agtype_value *aval; agtype_value *v = NULL; if (AGT_ROOT_IS_SCALAR(agt)) { agt = agtype_value_to_agtype(extract_entity_properties(agt, false)); } if (AGT_ROOT_IS_SCALAR(key)) { aval = get_ith_agtype_value_from_container(&key->root, 0); } else { PG_RETURN_BOOL(false); } if (AGT_ROOT_IS_OBJECT(agt) && aval->type == AGTV_STRING) { v = find_agtype_value_from_container(&agt->root, AGT_FOBJECT, aval); } else if (AGT_ROOT_IS_ARRAY(agt) && aval->type != AGTV_NULL) { v = find_agtype_value_from_container(&agt->root, AGT_FARRAY, aval); } PG_RETURN_BOOL(v != NULL); } PG_FUNCTION_INFO_V1(agtype_exists_any_agtype); /* * ?| operator for agtype. Returns true if any of the array strings exist as * top-level keys */ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; if (AGT_ROOT_IS_SCALAR(agt)) { agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) { while ((it = get_next_list_element(it, &keys->root, &elem))) { if (IS_A_AGTYPE_SCALAR(&elem)) { if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, AGT_FOBJECT, &elem)) { PG_RETURN_BOOL(true); } else if (AGT_ROOT_IS_ARRAY(agt) && (&elem)->type != AGTV_NULL && find_agtype_value_from_container(&agt->root, AGT_FARRAY, &elem)) { PG_RETURN_BOOL(true); } } else { PG_RETURN_BOOL(false); } } } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid agtype value for right operand"))); } PG_RETURN_BOOL(false); } PG_FUNCTION_INFO_V1(agtype_exists_all_agtype); /* * ?& operator for agtype. Returns true if all of the array strings exist as * top-level keys */ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; if (AGT_ROOT_IS_SCALAR(agt)) { agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) { while ((it = get_next_list_element(it, &keys->root, &elem))) { if (IS_A_AGTYPE_SCALAR(&elem)) { if ((&elem)->type == AGTV_NULL) { continue; } else if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, AGT_FOBJECT, &elem)) { continue; } else if (AGT_ROOT_IS_ARRAY(agt) && find_agtype_value_from_container(&agt->root, AGT_FARRAY, &elem)) { continue; } else { PG_RETURN_BOOL(false); } } else { PG_RETURN_BOOL(false); } } } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid agtype value for right operand"))); } PG_RETURN_BOOL(true); } PG_FUNCTION_INFO_V1(agtype_contains); /* * @> operator for agtype. Returns true if the right agtype path/value entries * contained at the top level within the left agtype value */ Datum agtype_contains(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it = NULL; agtype_iterator *property_it = NULL; agtype *properties = NULL; agtype *constraints = NULL; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { PG_RETURN_BOOL(false); } properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); if (AGT_ROOT_IS_SCALAR(properties) && AGTE_IS_AGTYPE(properties->root.children[0])) { properties = agtype_value_to_agtype(extract_entity_properties(properties, false)); } if (AGT_ROOT_IS_SCALAR(constraints) && AGTE_IS_AGTYPE(constraints->root.children[0])) { constraints = agtype_value_to_agtype(extract_entity_properties(constraints, false)); } if (AGT_ROOT_IS_OBJECT(properties) != AGT_ROOT_IS_OBJECT(constraints)) { PG_RETURN_BOOL(false); } property_it = agtype_iterator_init(&properties->root); constraint_it = agtype_iterator_init(&constraints->root); PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it)); } PG_FUNCTION_INFO_V1(agtype_contained_by); /* * <@ operator for agtype. Returns true if the left agtype path/value entries * contained at the top level within the right agtype value */ Datum agtype_contained_by(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it, *property_it; agtype *properties, *constraints; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { PG_RETURN_BOOL(false); } properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); if (AGT_ROOT_IS_SCALAR(properties) && AGTE_IS_AGTYPE(properties->root.children[0])) { properties = agtype_value_to_agtype(extract_entity_properties(properties, false)); } if (AGT_ROOT_IS_SCALAR(constraints) && AGTE_IS_AGTYPE(constraints->root.children[0])) { constraints = agtype_value_to_agtype(extract_entity_properties(constraints, false)); } constraint_it = agtype_iterator_init(&constraints->root); property_it = agtype_iterator_init(&properties->root); PG_RETURN_BOOL(agtype_deep_contains(&constraint_it, &property_it)); } PG_FUNCTION_INFO_V1(agtype_exists); /* * ? operator for agtype. Returns true if the string exists as top-level keys */ Datum agtype_exists(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); text *key = PG_GETARG_TEXT_PP(1); agtype_value aval; agtype_value *v = NULL; /* * We only match Object keys (which are naturally always Strings), or * string elements in arrays. In particular, we do not match non-string * scalar elements. Existence of a key/element is only considered at the * top level. No recursion occurs. */ aval.type = AGTV_STRING; aval.val.string.val = VARDATA_ANY(key); aval.val.string.len = VARSIZE_ANY_EXHDR(key); v = find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &aval); PG_RETURN_BOOL(v != NULL); } PG_FUNCTION_INFO_V1(agtype_exists_any); /* * ?| operator for agtype. Returns true if any of the array strings exist as * top-level keys */ Datum agtype_exists_any(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); int i; Datum *key_datums; bool *key_nulls; int elem_count; deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &elem_count); for (i = 0; i < elem_count; i++) { agtype_value strVal; if (key_nulls[i]) { continue; } strVal.type = AGTV_STRING; strVal.val.string.val = VARDATA(key_datums[i]); strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) != NULL) { PG_RETURN_BOOL(true); } } PG_RETURN_BOOL(false); } PG_FUNCTION_INFO_V1(agtype_exists_all); /* * ?& operator for agtype. Returns true if all of the array strings exist as * top-level keys */ Datum agtype_exists_all(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); int i; Datum *key_datums; bool *key_nulls; int elem_count; deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &elem_count); for (i = 0; i < elem_count; i++) { agtype_value strVal; if (key_nulls[i]) { continue; } strVal.type = AGTV_STRING; strVal.val.string.val = VARDATA(key_datums[i]); strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) == NULL) { PG_RETURN_BOOL(false); } } PG_RETURN_BOOL(true); } PG_FUNCTION_INFO_V1(agtype_concat); Datum agtype_concat(PG_FUNCTION_ARGS) { agtype *agt_lhs = AG_GET_ARG_AGTYPE_P(0); agtype *agt_rhs = AG_GET_ARG_AGTYPE_P(1); /* * Jsonb returns NULL for PG Null, but not for jsonb's NULL value, * so we do the same. */ if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { PG_RETURN_NULL(); } AG_RETURN_AGTYPE_P(agtype_concat_impl(agt_lhs, agt_rhs)); } static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2) { agtype_parse_state *state = NULL; agtype_value *res; agtype_iterator *it1; agtype_iterator *it2; /* * If one of the agtype is empty, just return the other if it's not scalar * and both are of the same kind. If it's a scalar or they are of * different kinds we need to perform the concatenation even if one is * empty. */ if (AGT_ROOT_IS_OBJECT(agt1) == AGT_ROOT_IS_OBJECT(agt2)) { if (AGT_ROOT_COUNT(agt1) == 0 && !AGT_ROOT_IS_SCALAR(agt2)) { return agt2; } else if (AGT_ROOT_COUNT(agt2) == 0 && !AGT_ROOT_IS_SCALAR(agt1)) { return agt1; } } it1 = agtype_iterator_init(&agt1->root); it2 = agtype_iterator_init(&agt2->root); res = iterator_concat(&it1, &it2, &state); Assert(res != NULL); return (agtype_value_to_agtype(res)); } /* * Iterate over all agtype objects and merge them into one. * The logic of this function copied from the same hstore function, * except the case, when it1 & it2 represents jbvObject. * In that case we just append the content of it2 to it1 without any * verifications. */ static agtype_value *iterator_concat(agtype_iterator **it1, agtype_iterator **it2, agtype_parse_state **state) { agtype_value v1, v2, *res = NULL; agtype_iterator_token r1, r2, rk1, rk2; r1 = rk1 = agtype_iterator_next(it1, &v1, false); r2 = rk2 = agtype_iterator_next(it2, &v2, false); /* * Both elements are objects. */ if (rk1 == WAGT_BEGIN_OBJECT && rk2 == WAGT_BEGIN_OBJECT) { /* * Append all tokens from v1 to res, except last WAGT_END_OBJECT * (because res will not be finished yet). */ push_agtype_value(state, r1, NULL); while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_OBJECT) { Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); push_agtype_value(state, r1, &v1); } /* * Append all tokens from v2 to res, except last WAGT_END_OBJECT */ while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_OBJECT) { Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); push_agtype_value(state, r2, &v2); } /* * Append the last token WAGT_END_OBJECT to complete res */ res = push_agtype_value(state, WAGT_END_OBJECT, NULL); } /* * Both elements are arrays (either can be scalar). */ else if (rk1 == WAGT_BEGIN_ARRAY && rk2 == WAGT_BEGIN_ARRAY) { push_agtype_value(state, r1, NULL); while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_ARRAY) { Assert(r1 == WAGT_ELEM); push_agtype_value(state, r1, &v1); } while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_ARRAY) { Assert(r2 == WAGT_ELEM); push_agtype_value(state, r2, &v2); } res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } /* have we got array || object or object || array? */ else if (((rk1 == WAGT_BEGIN_ARRAY && !(*it1)->is_scalar) && rk2 == WAGT_BEGIN_OBJECT) || (rk1 == WAGT_BEGIN_OBJECT && (rk2 == WAGT_BEGIN_ARRAY && !(*it2)->is_scalar))) { agtype_iterator **it_array = rk1 == WAGT_BEGIN_ARRAY ? it1 : it2; agtype_iterator **it_object = rk1 == WAGT_BEGIN_OBJECT ? it1 : it2; bool prepend = (rk1 == WAGT_BEGIN_OBJECT); push_agtype_value(state, WAGT_BEGIN_ARRAY, NULL); if (prepend) { push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); while ((r1 = agtype_iterator_next(it_object, &v1, true)) != WAGT_END_OBJECT) { Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); push_agtype_value(state, r1, &v1); } push_agtype_value(state, WAGT_END_OBJECT, NULL); while ((r2 = agtype_iterator_next(it_array, &v2, true)) != WAGT_END_ARRAY) { Assert(r2 == WAGT_ELEM); push_agtype_value(state, r2, &v2); } res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } else { while ((r1 = agtype_iterator_next(it_array, &v1, true)) != WAGT_END_ARRAY) { Assert(r1 == WAGT_ELEM); push_agtype_value(state, r1, &v1); } push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); while ((r2 = agtype_iterator_next(it_object, &v2, true)) != WAGT_END_OBJECT) { Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); push_agtype_value(state, r2,&v2); } push_agtype_value(state, WAGT_END_OBJECT, NULL); res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } } else if (rk1 == WAGT_BEGIN_OBJECT) { /* * We have object || array. */ Assert(rk1 == WAGT_BEGIN_OBJECT); Assert(rk2 == WAGT_BEGIN_ARRAY); push_agtype_value(state, WAGT_BEGIN_ARRAY, NULL); push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_OBJECT) { Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); push_agtype_value(state, r1, &v1); } push_agtype_value(state, WAGT_END_OBJECT, NULL); while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_ARRAY) { if (v2.type < AGTV_VERTEX || v2.type > AGTV_PATH) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid right operand for agtype " "concatenation"))); } Assert(r2 == WAGT_ELEM); push_agtype_value(state, r2, &v2); } res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } else { /* * We have array || object. */ Assert(rk1 == WAGT_BEGIN_ARRAY); Assert(rk2 == WAGT_BEGIN_OBJECT); push_agtype_value(state, WAGT_BEGIN_ARRAY, NULL); while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_ARRAY) { if (v1.type < AGTV_VERTEX || v1.type > AGTV_PATH) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid left operand for agtype " "concatenation"))); } Assert(r1 == WAGT_ELEM); push_agtype_value(state, r1, &v1); } push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_OBJECT) { Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); push_agtype_value(state, r2, &v2); } push_agtype_value(state, WAGT_END_OBJECT, NULL); res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } return res; } /* * agtype path extraction operator '#>'. The right operand can * either be an array of object keys or array indexes for extracting * agtype sub-object or sub-array from the left operand. */ PG_FUNCTION_INFO_V1(agtype_extract_path); Datum agtype_extract_path(PG_FUNCTION_ARGS) { return get_agtype_path_all(fcinfo, false); } /* * agtype path extraction operator '#>>' that returns the extracted path * as text. */ PG_FUNCTION_INFO_V1(agtype_extract_path_text); Datum agtype_extract_path_text(PG_FUNCTION_ARGS) { return get_agtype_path_all(fcinfo, true); } static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *path = AG_GET_ARG_AGTYPE_P(1); agtype *res; int npath; int i; bool have_object = false, have_array = false; agtype_value *agtvp = NULL; agtype_value tv; agtype_container *container; if (AGT_ROOT_IS_SCALAR(path) || AGT_ROOT_IS_OBJECT(path)) { ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right operand must be an array"))); } if (AGT_ROOT_IS_SCALAR(agt)) { agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); } npath = AGT_ROOT_COUNT(path); container = &agt->root; /* Identify whether we have object, array, or scalar at top-level */ if (AGT_ROOT_IS_OBJECT(agt)) { have_object = true; } else if (AGT_ROOT_IS_ARRAY(agt) && !AGT_ROOT_IS_SCALAR(agt)) { have_array = true; } else { Assert(AGT_ROOT_IS_ARRAY(agt) && AGT_ROOT_IS_SCALAR(agt)); /* Extract the scalar value */ if (npath <= 0) { agtvp = get_ith_agtype_value_from_container(container, 0); } } /* * If RHS array is empty, return the entire LHS object/array, based on the * assumption that we should not do any field or element extractions. In * case of non-scalar, we can just hand back the agtype without much * work but for the scalar case, fall through and deal with the value * below the loop (This inconsistency arises because there's no easy way to * generate an agtype_value directly for root-level containers) */ if (npath <= 0 && agtvp == NULL) { if (as_text) { PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, container, VARSIZE(agt)))); } else { /* not text mode - just hand back the agtype */ AG_RETURN_AGTYPE_P(agt); } } for (i = 0; i < npath; i++) { agtype_value *cur_key = get_ith_agtype_value_from_container(&path->root, i); if (have_object && cur_key->type == AGTV_STRING) { agtvp = find_agtype_value_from_container(container, AGT_FOBJECT, cur_key); } else if (have_array) { long lindex; uint32 index; /* * for array on LHS, there should be an integer or a * valid integer string on RHS */ if (cur_key->type == AGTV_INTEGER) { lindex = cur_key->val.int_value; } else if (cur_key->type == AGTV_STRING) { /* * extract the integer from the string, * if character other than a digit is found, return null */ char* str = NULL; lindex = strtol(cur_key->val.string.val, &str, 10); if (strcmp(str, "")) { PG_RETURN_NULL(); } } else { PG_RETURN_NULL(); } if (lindex > INT_MAX || lindex < INT_MIN) { PG_RETURN_NULL(); } if (lindex >= 0) { index = (uint32) lindex; } else { /* Handle negative subscript */ uint32 nelements; /* Container must be an array, but make sure */ if (!AGTYPE_CONTAINER_IS_ARRAY(container)) { elog(ERROR, "not an agtype array"); } nelements = AGTYPE_CONTAINER_SIZE(container); if (-lindex > nelements) { PG_RETURN_NULL(); } else { index = nelements + lindex; } } agtvp = get_ith_agtype_value_from_container(container, index); } else { PG_RETURN_NULL(); } if (agtvp == NULL) { PG_RETURN_NULL(); } else if (i == npath - 1) { break; } if (agtvp->type == AGTV_BINARY) { agtype_iterator_token r; agtype_iterator *it = agtype_iterator_init((agtype_container *) agtvp->val.binary.data); r = agtype_iterator_next(&it, &tv, true); container = (agtype_container *) agtvp->val.binary.data; have_object = r == WAGT_BEGIN_OBJECT; have_array = r == WAGT_BEGIN_ARRAY; } else { have_object = agtvp->type == AGTV_OBJECT; have_array = agtvp->type == AGTV_ARRAY; } } if (as_text) { /* special-case output for string and null values */ if (agtvp->type == AGTV_STRING) { PG_RETURN_TEXT_P(cstring_to_text_with_len(agtvp->val.string.val, agtvp->val.string.len)); } if (agtvp->type == AGTV_NULL) { PG_RETURN_NULL(); } } res = agtype_value_to_agtype(agtvp); if (as_text) { PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, &res->root, VARSIZE(res)))); } else { /* not text mode - just hand back the agtype */ AG_RETURN_AGTYPE_P(res); } }