<para>
The <literal>@?</literal> and <literal>@@</literal> operators suppress
errors including: lacking object field or array element, unexpected JSON
- item type.
+ item type and numeric errors.
This behavior might be helpful while searching over JSON document
collections of varying structure.
</para>
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
}
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error) \
+do { \
+ if (have_error) { \
+ *have_error = true; \
+ return 0.0; \
+ } else { \
+ throw_error; \
+ } \
+} while (0)
+
/*
- * float8in_internal - guts of float8in()
+ * float8in_internal_opt_error - guts of float8in()
*
* This is exposed for use by functions that want a reasonably
* platform-independent way of inputting doubles. The behavior is
*
* "num" could validly be declared "const char *", but that results in an
* unreasonable amount of extra casting both here and in callers, so we don't.
+ *
+ * When "*have_error" flag is provided, it's set instead of throwing an
+ * error. This is helpful when caller need to handle errors by itself.
*/
double
-float8in_internal(char *num, char **endptr_p,
- const char *type_name, const char *orig_string)
+float8in_internal_opt_error(char *num, char **endptr_p,
+ const char *type_name, const char *orig_string,
+ bool *have_error)
{
double val;
char *endptr;
* strtod() on different platforms.
*/
if (*num == '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- type_name, orig_string)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ type_name, orig_string))));
errno = 0;
val = strtod(num, &endptr);
char *errnumber = pstrdup(num);
errnumber[endptr - num] = '\0';
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("\"%s\" is out of range for type double precision",
- errnumber)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("\"%s\" is out of range for "
+ "type double precision",
+ errnumber))));
}
}
else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- type_name, orig_string)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type "
+ "%s: \"%s\"",
+ type_name, orig_string))));
}
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
else
if (endptr_p)
*endptr_p = endptr;
else if (*endptr != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- type_name, orig_string)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type "
+ "%s: \"%s\"",
+ type_name, orig_string))));
return val;
}
+/*
+ * Interfact to float8in_internal_opt_error() without "have_error" argument.
+ */
+double
+float8in_internal(char *num, char **endptr_p,
+ const char *type_name, const char *orig_string)
+{
+ return float8in_internal_opt_error(num, endptr_p, type_name,
+ orig_string, NULL);
+}
+
+
/*
* float8out - converts float8 number to a string
* using a standard output format
JsonbValue *larg,
JsonbValue *rarg,
void *param);
+typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
Jsonb *json, bool throwErrors, JsonValueList *result);
JsonbValue *jb, bool unwrapRightArg,
JsonPathPredicateCallback exec, void *param);
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
- JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb,
+ BinaryArithmFunc func, JsonValueList *found);
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
JsonValueList *found);
case jpiAdd:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_add, found);
+ numeric_add_opt_error, found);
case jpiSub:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_sub, found);
+ numeric_sub_opt_error, found);
case jpiMul:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_mul, found);
+ numeric_mul_opt_error, found);
case jpiDiv:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_div, found);
+ numeric_div_opt_error, found);
case jpiMod:
return executeBinaryArithmExpr(cxt, jsp, jb,
- numeric_mod, found);
+ numeric_mod_opt_error, found);
case jpiPlus:
return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
{
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric)));
+ bool have_error = false;
- (void) float8in_internal(tmp,
- NULL,
- "double precision",
- tmp);
+ (void) float8in_internal_opt_error(tmp,
+ NULL,
+ "double precision",
+ tmp,
+ &have_error);
+ if (have_error)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
+ errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
+ errdetail("jsonpath item method .%s() "
+ "can only be applied to "
+ "a numeric value",
+ jspOperationName(jsp->type)))));
res = jperOk;
}
else if (jb->type == jbvString)
double val;
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
+ bool have_error = false;
- val = float8in_internal(tmp,
- NULL,
- "double precision",
- tmp);
+ val = float8in_internal_opt_error(tmp,
+ NULL,
+ "double precision",
+ tmp,
+ &have_error);
- if (isinf(val))
+ if (have_error || isinf(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
*/
static JsonPathExecResult
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, PGFunction func,
+ JsonbValue *jb, BinaryArithmFunc func,
JsonValueList *found)
{
JsonPathExecResult jper;
JsonValueList rseq = {0};
JsonbValue *lval;
JsonbValue *rval;
- Datum res;
+ Numeric res;
jspGetLeftArg(jsp, &elem);
"is not a singleton numeric value",
jspOperationName(jsp->type)))));
- res = DirectFunctionCall2(func,
- NumericGetDatum(lval->val.numeric),
- NumericGetDatum(rval->val.numeric));
+ if (jspThrowErrors(cxt))
+ {
+ res = func(lval->val.numeric, rval->val.numeric, NULL);
+ }
+ else
+ {
+ bool error = false;
+
+ res = func(lval->val.numeric, rval->val.numeric, &error);
+
+ if (error)
+ return jperError;
+ }
if (!jspGetNext(jsp, &elem) && !found)
return jperOk;
lval = palloc(sizeof(*lval));
lval->type = jbvNumeric;
- lval->val.numeric = DatumGetNumeric(res);
+ lval->val.numeric = res;
return executeNextItem(cxt, jsp, &elem, lval, found, false);
}
JsonValueList found = {0};
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
Datum numeric_index;
+ bool have_error = false;
if (jperIsError(res))
return res;
NumericGetDatum(jbv->val.numeric),
Int32GetDatum(0));
- *index = DatumGetInt32(DirectFunctionCall1(numeric_int4, numeric_index));
+ *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
+ &have_error);
+
+ if (have_error)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
+ errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
+ errdetail("jsonpath array subscript is "
+ "out of integer range"))));
return jperOk;
}
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
static Numeric make_result(const NumericVar *var);
+static Numeric make_result_opt_error(const NumericVar *var, bool *error);
static void apply_typmod(NumericVar *var, int32 typmod);
-static int32 numericvar_to_int32(const NumericVar *var);
+static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
static void int64_to_numericvar(int64 val, NumericVar *var);
#ifdef HAVE_INT128
}
/* if result exceeds the range of a legal int4, we ereport here */
- result = numericvar_to_int32(&result_var);
+ if (!numericvar_to_int32(&result_var, &result))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("integer out of range")));
free_var(&count_var);
free_var(&result_var);
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
+ Numeric res;
+
+ res = numeric_add_opt_error(num1, num2, NULL);
+
+ PG_RETURN_NUMERIC(res);
+}
+
+/*
+ * numeric_add_opt_error() -
+ *
+ * Internal version of numeric_add(). If "*have_error" flag is provided,
+ * on error it's set to true, NULL returned. This is helpful when caller
+ * need to handle errors by itself.
+ */
+Numeric
+numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error)
+{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ return make_result(&const_nan);
/*
* Unpack the values, let add_var() compute the result and return it.
init_var(&result);
add_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = make_result_opt_error(&result, have_error);
free_var(&result);
- PG_RETURN_NUMERIC(res);
+ return res;
}
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
+ Numeric res;
+
+ res = numeric_sub_opt_error(num1, num2, NULL);
+
+ PG_RETURN_NUMERIC(res);
+}
+
+
+/*
+ * numeric_sub_opt_error() -
+ *
+ * Internal version of numeric_sub(). If "*have_error" flag is provided,
+ * on error it's set to true, NULL returned. This is helpful when caller
+ * need to handle errors by itself.
+ */
+Numeric
+numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error)
+{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ return make_result(&const_nan);
/*
* Unpack the values, let sub_var() compute the result and return it.
init_var(&result);
sub_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = make_result_opt_error(&result, have_error);
free_var(&result);
- PG_RETURN_NUMERIC(res);
+ return res;
}
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
+ Numeric res;
+
+ res = numeric_mul_opt_error(num1, num2, NULL);
+
+ PG_RETURN_NUMERIC(res);
+}
+
+
+/*
+ * numeric_mul_opt_error() -
+ *
+ * Internal version of numeric_mul(). If "*have_error" flag is provided,
+ * on error it's set to true, NULL returned. This is helpful when caller
+ * need to handle errors by itself.
+ */
+Numeric
+numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
+{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ return make_result(&const_nan);
/*
* Unpack the values, let mul_var() compute the result and return it.
init_var(&result);
mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
- res = make_result(&result);
+ res = make_result_opt_error(&result, have_error);
free_var(&result);
- PG_RETURN_NUMERIC(res);
+ return res;
}
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
+ Numeric res;
+
+ res = numeric_div_opt_error(num1, num2, NULL);
+
+ PG_RETURN_NUMERIC(res);
+}
+
+
+/*
+ * numeric_div_opt_error() -
+ *
+ * Internal version of numeric_div(). If "*have_error" flag is provided,
+ * on error it's set to true, NULL returned. This is helpful when caller
+ * need to handle errors by itself.
+ */
+Numeric
+numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
+{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ return make_result(&const_nan);
/*
* Unpack the arguments
*/
rscale = select_div_scale(&arg1, &arg2);
+ /*
+ * If "have_error" is provided, check for division by zero here
+ */
+ if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0))
+ {
+ *have_error = true;
+ return NULL;
+ }
+
/*
* Do the divide and return the result
*/
div_var(&arg1, &arg2, &result, rscale, true);
- res = make_result(&result);
+ res = make_result_opt_error(&result, have_error);
free_var(&result);
- PG_RETURN_NUMERIC(res);
+ return res;
}
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
+
+ res = numeric_mod_opt_error(num1, num2, NULL);
+
+ PG_RETURN_NUMERIC(res);
+}
+
+
+/*
+ * numeric_mod_opt_error() -
+ *
+ * Internal version of numeric_mod(). If "*have_error" flag is provided,
+ * on error it's set to true, NULL returned. This is helpful when caller
+ * need to handle errors by itself.
+ */
+Numeric
+numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
+{
+ Numeric res;
NumericVar arg1;
NumericVar arg2;
NumericVar result;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ return make_result(&const_nan);
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
+ /*
+ * If "have_error" is provided, check for division by zero here
+ */
+ if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0))
+ {
+ *have_error = true;
+ return NULL;
+ }
+
mod_var(&arg1, &arg2, &result);
- res = make_result(&result);
+ res = make_result_opt_error(&result, NULL);
free_var(&result);
- PG_RETURN_NUMERIC(res);
+ return res;
}
PG_RETURN_NUMERIC(res);
}
-
-Datum
-numeric_int4(PG_FUNCTION_ARGS)
+int32
+numeric_int4_opt_error(Numeric num, bool *have_error)
{
- Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int32 result;
/* XXX would it be better to return NULL? */
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert NaN to integer")));
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert NaN to integer")));
+ }
+ }
/* Convert to variable format, then convert to int4 */
init_var_from_num(num, &x);
- result = numericvar_to_int32(&x);
- PG_RETURN_INT32(result);
+
+ if (!numericvar_to_int32(&x, &result))
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return 0;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("integer out of range")));
+ }
+ }
+
+ return result;
+}
+
+Datum
+numeric_int4(PG_FUNCTION_ARGS)
+{
+ Numeric num = PG_GETARG_NUMERIC(0);
+
+ PG_RETURN_INT32(numeric_int4_opt_error(num, NULL));
}
/*
* Given a NumericVar, convert it to an int32. If the NumericVar
- * exceeds the range of an int32, raise the appropriate error via
- * ereport(). The input NumericVar is *not* free'd.
+ * exceeds the range of an int32, false is returned, otherwise true is returned.
+ * The input NumericVar is *not* free'd.
*/
-static int32
-numericvar_to_int32(const NumericVar *var)
+static bool
+numericvar_to_int32(const NumericVar *var, int32 *result)
{
- int32 result;
int64 val;
if (!numericvar_to_int64(var, &val))
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("integer out of range")));
+ return false;
/* Down-convert to int4 */
- result = (int32) val;
+ *result = (int32) val;
/* Test for overflow by reverse-conversion. */
- if ((int64) result != val)
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("integer out of range")));
-
- return result;
+ return ((int64) *result == val);
}
Datum
/*
- * make_result() -
+ * make_result_opt_error() -
*
* Create the packed db numeric format in palloc()'d memory from
- * a variable.
+ * a variable. If "*have_error" flag is provided, on error it's set to
+ * true, NULL returned. This is helpful when caller need to handle errors
+ * by itself.
*/
static Numeric
-make_result(const NumericVar *var)
+make_result_opt_error(const NumericVar *var, bool *have_error)
{
Numeric result;
NumericDigit *digits = var->digits;
/* Check for overflow of int16 fields */
if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale)
- ereport(ERROR,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return NULL;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format")));
+ }
+ }
dump_numeric("make_result()", result);
return result;
}
+/*
+ * make_result() -
+ *
+ * An interface to make_result_opt_error() without "have_error" argument.
+ */
+static Numeric
+make_result(const NumericVar *var)
+{
+ return make_result_opt_error(var, NULL);
+}
+
+
/*
* apply_typmod() -
*
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string);
+extern float8 float8in_internal_opt_error(char *num, char **endptr_p,
+ const char *type_name, const char *orig_string,
+ bool *have_error);
extern char *float8out_internal(float8 num);
extern int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b);
extern char *numeric_out_sci(Numeric num, int scale);
extern char *numeric_normalize(Numeric num);
+extern Numeric numeric_add_opt_error(Numeric num1, Numeric num2,
+ bool *have_error);
+extern Numeric numeric_sub_opt_error(Numeric num1, Numeric num2,
+ bool *have_error);
+extern Numeric numeric_mul_opt_error(Numeric num1, Numeric num2,
+ bool *have_error);
+extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
+ bool *have_error);
+extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
+ bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+
#endif /* _PG_NUMERIC_H_ */
(0 rows)
select jsonb '[1]' @? 'lax $[10000000000000000]';
-ERROR: integer out of range
+ ?column?
+----------
+
+(1 row)
+
select jsonb '[1]' @? 'strict $[10000000000000000]';
-ERROR: integer out of range
+ ?column?
+----------
+
+(1 row)
+
select jsonb_path_query('[1]', 'lax $[10000000000000000]');
-ERROR: integer out of range
+ERROR: invalid SQL/JSON subscript
+DETAIL: jsonpath array subscript is out of integer range
select jsonb_path_query('[1]', 'strict $[10000000000000000]');
-ERROR: integer out of range
+ERROR: invalid SQL/JSON subscript
+DETAIL: jsonpath array subscript is out of integer range
select jsonb '[1]' @? '$[0]';
?column?
----------
-- arithmetic errors
select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
-ERROR: division by zero
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
-ERROR: division by zero
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
select jsonb_path_query('0', '1 / $');
ERROR: division by zero
select jsonb_path_query('0', '1 / $ + 2');
(1 row)
select jsonb_path_query('"1.23aaa"', '$.double()');
-ERROR: invalid input syntax for type double precision: "1.23aaa"
+ERROR: non-numeric SQL/JSON item
+DETAIL: jsonpath item method .double() can only be applied to a numeric value
select jsonb_path_query('"nan"', '$.double()');
jsonb_path_query
------------------