| /* |
| * 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 <math.h> |
| #include <limits.h> |
| |
| #include "catalog/pg_type_d.h" |
| #include "fmgr.h" |
| #include "utils/builtins.h" |
| #include "utils/numeric.h" |
| |
| #include "utils/agtype.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); |
| } |
| } |