Skip to content

Commit c339d07

Browse files
authored
Reimplement ISNUMERIC() function in C language to improve performance (#3765)
To improve the performance of queries with ISNUMERIC() predicates, this commit reimplements the ISNUMERIC() function from PL/pgSQL to C language. The new implementation uses InputFunctionCallSafe for safer and more efficient type checking and conversion. Additionally, the money data type's input function has been updated to report invalid input as a "soft" error for InputFunctionCallSafe, avoiding unnecessary transaction aborts. This commit also changes the ISNUMERIC() function attributes to IMMUTABLE and PARALLEL SAFE for better query optimisation opportunities. Task : BABEL-5696 Signed-off-by: Sumit Jaiswal <[email protected]>
1 parent 86a9ec2 commit c339d07

File tree

10 files changed

+1525
-90
lines changed

10 files changed

+1525
-90
lines changed

contrib/babelfishpg_money/fixeddecimal.c

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ typedef struct FixedDecimalAggState
229229

230230
static char *pg_int64tostr(char *str, int64 value);
231231
static char *pg_int64tostr_zeropad(char *str, int64 value, int64 padding);
232-
static void apply_typmod(int64 value, int32 typmod, int precision, int scale);
233-
static int64 scanfixeddecimal(const char *str, int *precision, int *scale);
232+
static bool apply_typmod(int64 value, int32 typmod, int precision, int scale, FunctionCallInfo *fcinfo);
233+
static int64 scanfixeddecimal(const char *str, int *precision, int *scale, FunctionCallInfo *fcinfo);
234234
static FixedDecimalAggState *makeFixedDecimalAggState(FunctionCallInfo fcinfo);
235235
static void fixeddecimal_accum(FixedDecimalAggState *state, int64 newval);
236236
static int64 int8fixeddecimal_internal(int64 arg, const char *typename);
@@ -445,7 +445,7 @@ fixeddecimal2str(int64 val, char *buffer,
445445
* scanfixeddecimal --- try to parse a string into a fixeddecimal.
446446
*/
447447
static int64
448-
scanfixeddecimal(const char *str, int *precision, int *scale)
448+
scanfixeddecimal(const char *str, int *precision, int *scale, FunctionCallInfo *fcinfo)
449449
{
450450
const char *ptr = str;
451451
int64 integralpart = 0;
@@ -454,6 +454,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
454454
int vprecision = 0;
455455
int vscale = 0;
456456
bool has_seen_sign = false;
457+
Node *escontext = (*fcinfo)->context;
457458

458459
/*
459460
* Do our own scan, rather than relying on sscanf which might be broken
@@ -500,7 +501,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
500501
*/
501502
if ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z'))
502503
{
503-
ereport(ERROR,
504+
ereturn(escontext, (Datum) 0,
504505
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
505506
errmsg("invalid characters found: cannot cast value \"%s\" to money",
506507
str)));
@@ -520,7 +521,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
520521
{
521522
if (has_seen_sign)
522523
{
523-
ereport(ERROR,
524+
ereturn(escontext, (Datum) 0,
524525
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
525526
errmsg("invalid characters found: cannot cast value \"%s\" to money",
526527
str)));
@@ -539,7 +540,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
539540
vprecision++;
540541
if ((tmp / 10) != integralpart) /* underflow? */
541542
{
542-
ereport(ERROR,
543+
ereturn(escontext, (Datum) 0,
543544
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
544545
errmsg("value \"%s\" is out of range for type fixeddecimal",
545546
str)));
@@ -559,7 +560,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
559560
{
560561
if (has_seen_sign)
561562
{
562-
ereport(ERROR,
563+
ereturn(escontext, (Datum) 0,
563564
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
564565
errmsg("invalid characters found: cannot cast value \"%s\" to money",
565566
str)));
@@ -583,7 +584,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
583584
vprecision++;
584585
if ((tmp / 10) != integralpart) /* overflow? */
585586
{
586-
ereport(ERROR,
587+
ereturn(escontext, (Datum) 0,
587588
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
588589
errmsg("value \"%s\" is out of range for type fixeddecimal",
589590
str)));
@@ -628,7 +629,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
628629
ptr++;
629630

630631
if (*ptr != '\0')
631-
ereport(ERROR,
632+
ereturn(escontext, (Datum) 0,
632633
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
633634
errmsg("value \"%s\" is out of range for type fixeddecimal", str)));
634635

@@ -644,13 +645,13 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
644645
int64 multiplier = FIXEDDECIMAL_MULTIPLIER;
645646

646647
if (__builtin_mul_overflow(integralpart, multiplier, &value))
647-
ereport(ERROR,
648+
ereturn(escontext, (Datum) 0,
648649
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
649650
errmsg("value \"%s\" is out of range for type fixeddecimal",
650651
str)));
651652

652653
if (__builtin_sub_overflow(value, fractionalpart, &value))
653-
ereport(ERROR,
654+
ereturn(escontext, (Datum) 0,
654655
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
655656
errmsg("value \"%s\" is out of range for type fixeddecimal",
656657
str)));
@@ -661,7 +662,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
661662
if (value != 0 && (!SAMESIGN(value, integralpart) ||
662663
!SAMESIGN(value - fractionalpart, value) ||
663664
!SAMESIGN(value - fractionalpart, value)))
664-
ereport(ERROR,
665+
ereturn(escontext, (Datum) 0,
665666
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
666667
errmsg("value \"%s\" is out of range for type fixeddecimal",
667668
str)));
@@ -676,13 +677,13 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
676677

677678
#ifdef HAVE_BUILTIN_OVERFLOW
678679
if (__builtin_mul_overflow(integralpart, FIXEDDECIMAL_MULTIPLIER, &value))
679-
ereport(ERROR,
680+
ereturn(escontext, (Datum) 0,
680681
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
681682
errmsg("value \"%s\" is out of range for type fixeddecimal",
682683
str)));
683684

684685
if (__builtin_add_overflow(value, fractionalpart, &value))
685-
ereport(ERROR,
686+
ereturn(escontext, (Datum) 0,
686687
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
687688
errmsg("value \"%s\" is out of range for type fixeddecimal",
688689
str)));
@@ -692,7 +693,7 @@ scanfixeddecimal(const char *str, int *precision, int *scale)
692693
if (value != 0 && (!SAMESIGN(value, integralpart) ||
693694
!SAMESIGN(value - fractionalpart, value) ||
694695
!SAMESIGN(value + fractionalpart, value)))
695-
ereport(ERROR,
696+
ereturn(escontext, (Datum) 0,
696697
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
697698
errmsg("value \"%s\" is out of range for type fixeddecimal",
698699
str)));
@@ -713,23 +714,24 @@ fixeddecimalin(PG_FUNCTION_ARGS)
713714
int32 typmod = PG_GETARG_INT32(2);
714715
int precision;
715716
int scale;
716-
int64 result = scanfixeddecimal(str, &precision, &scale);
717+
int64 result = scanfixeddecimal(str, &precision, &scale, &fcinfo);
717718

718-
apply_typmod(result, typmod, precision, scale);
719+
apply_typmod(result, typmod, precision, scale, &fcinfo);
719720

720721
PG_RETURN_INT64(result);
721722
}
722723

723-
static void
724-
apply_typmod(int64 value, int32 typmod, int precision, int scale)
724+
static bool
725+
apply_typmod(int64 value, int32 typmod, int precision, int scale, FunctionCallInfo *fcinfo)
725726
{
726727
int precisionlimit;
727728
int scalelimit;
728729
int maxdigits;
730+
Node *escontext = (*fcinfo)->context;
729731

730732
/* Do nothing if we have a default typmod (-1) */
731733
if (typmod < (int32) (VARHDRSZ))
732-
return;
734+
return true;
733735

734736
typmod -= VARHDRSZ;
735737
precisionlimit = (typmod >> 16) & 0xffff;
@@ -739,13 +741,13 @@ apply_typmod(int64 value, int32 typmod, int precision, int scale)
739741
if (scale > scalelimit)
740742

741743
if (scale != FIXEDDECIMAL_SCALE)
742-
ereport(ERROR,
744+
ereturn(escontext, false,
743745
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
744746
errmsg("FIXEDDECIMAL scale must be %d",
745747
FIXEDDECIMAL_SCALE)));
746748

747749
if (precision > maxdigits)
748-
ereport(ERROR,
750+
ereturn(escontext, false,
749751
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
750752
errmsg("FIXEDDECIMAL field overflow"),
751753
errdetail("A field with precision %d, scale %d must round to an absolute value less than %s%d.",
@@ -754,7 +756,7 @@ apply_typmod(int64 value, int32 typmod, int precision, int scale)
754756
maxdigits ? "10^" : "",
755757
maxdigits ? maxdigits : 1
756758
)));
757-
759+
return true;
758760
}
759761

760762
Datum
@@ -3136,7 +3138,7 @@ char_to_fixeddecimal(PG_FUNCTION_ARGS)
31363138
char *str = TextDatumGetCString(PG_GETARG_DATUM(0));
31373139
int precision;
31383140
int scale;
3139-
int64 result = scanfixeddecimal(str, &precision, &scale);
3141+
int64 result = scanfixeddecimal(str, &precision, &scale, &fcinfo);
31403142

31413143
PG_RETURN_INT64(result);
31423144
}

contrib/babelfishpg_tsql/runtime/functions.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "executor/spi.h"
3333
#include "executor/spi_priv.h"
3434
#include "miscadmin.h"
35+
#include "nodes/miscnodes.h"
3536
#include "parser/scansup.h"
3637
#include "tsearch/ts_locale.h"
3738
#include "utils/acl.h"
@@ -196,6 +197,7 @@ PG_FUNCTION_INFO_V1(datepart_internal_real);
196197
PG_FUNCTION_INFO_V1(datepart_internal_money);
197198
PG_FUNCTION_INFO_V1(datepart_internal_smallmoney);
198199
PG_FUNCTION_INFO_V1(replace_special_chars_fts);
200+
PG_FUNCTION_INFO_V1(isnumeric);
199201

200202
void *string_to_tsql_varchar(const char *input_str);
201203
void *get_servername_internal(void);
@@ -2149,6 +2151,137 @@ search_partition(PG_FUNCTION_ARGS)
21492151
PG_RETURN_INT32(result);
21502152
}
21512153

2154+
/*
2155+
* Structure to cache metadata needed in isnumeric().
2156+
*/
2157+
typedef struct IsNumericIOData
2158+
{
2159+
/* For numeric type */
2160+
FmgrInfo numeric_inputproc;
2161+
Oid numeric_typioparam;
2162+
2163+
/* For money type */
2164+
FmgrInfo money_inputproc;
2165+
Oid money_typoid;
2166+
Oid money_typioparam;
2167+
} IsNumericIOData;
2168+
2169+
/*
2170+
* isnumeric()
2171+
* Returns 1 if the input value is numeric or can be
2172+
* converted to a numeric value, and 0 otherwise.
2173+
2174+
* It directly returns 1 for known numeric types (including TSQL types).
2175+
* For other types, attempts conversion to numeric and money.
2176+
*/
2177+
Datum
2178+
isnumeric(PG_FUNCTION_ARGS)
2179+
{
2180+
Oid argtypeid;
2181+
Oid numeric_typiofunc;
2182+
Oid money_typiofunc;
2183+
char *value_str;
2184+
bool result = false;
2185+
Datum converted;
2186+
ErrorSaveContext numeric_escontext = {T_ErrorSaveContext};
2187+
ErrorSaveContext money_escontext = {T_ErrorSaveContext};
2188+
IsNumericIOData *my_extra;
2189+
2190+
2191+
if (PG_ARGISNULL(0))
2192+
PG_RETURN_INT32(0);
2193+
2194+
argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
2195+
2196+
/* Sanity check. */
2197+
if (!OidIsValid(argtypeid))
2198+
ereport(ERROR,
2199+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2200+
errmsg("could not determine input data type")));
2201+
2202+
/* Fast path for known numeric types. */
2203+
if (argtypeid != TEXTOID)
2204+
{
2205+
if ((*common_utility_plugin_ptr->is_tsql_tinyint_datatype)(argtypeid) ||
2206+
(*common_utility_plugin_ptr->is_tsql_money_datatype)(argtypeid) ||
2207+
(*common_utility_plugin_ptr->is_tsql_smallmoney_datatype)(argtypeid) ||
2208+
(*common_utility_plugin_ptr->is_tsql_decimal_datatype)(argtypeid) ||
2209+
(*common_utility_plugin_ptr->is_tsql_fixeddecimal_datatype)(argtypeid) ||
2210+
(argtypeid == FLOAT4OID) || (argtypeid == FLOAT8OID) ||
2211+
(argtypeid == NUMERICOID) || (argtypeid == INT2OID) ||
2212+
(argtypeid == INT4OID) || (argtypeid == INT8OID))
2213+
PG_RETURN_INT32(1);
2214+
}
2215+
2216+
/* Get or initialize the cached data. */
2217+
my_extra = (IsNumericIOData *) fcinfo->flinfo->fn_extra;
2218+
if (my_extra == NULL)
2219+
{
2220+
fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
2221+
sizeof(IsNumericIOData));
2222+
my_extra = (IsNumericIOData *) fcinfo->flinfo->fn_extra;
2223+
my_extra->money_typoid = InvalidOid;
2224+
}
2225+
2226+
/* Initialize the cached information for the first time. */
2227+
if (my_extra->money_typoid == InvalidOid)
2228+
{
2229+
/* Setup for money type. */
2230+
my_extra->money_typoid = (*common_utility_plugin_ptr->get_tsql_datatype_oid)("money");
2231+
getTypeInputInfo(my_extra->money_typoid,
2232+
&money_typiofunc,
2233+
&my_extra->money_typioparam);
2234+
fmgr_info_cxt(money_typiofunc,
2235+
&my_extra->money_inputproc,
2236+
fcinfo->flinfo->fn_mcxt);
2237+
2238+
/* Setup for numeric type. */
2239+
getTypeInputInfo(NUMERICOID,
2240+
&numeric_typiofunc,
2241+
&my_extra->numeric_typioparam);
2242+
fmgr_info_cxt(numeric_typiofunc,
2243+
&my_extra->numeric_inputproc,
2244+
fcinfo->flinfo->fn_mcxt);
2245+
}
2246+
2247+
/* Get the string representation from input datum. */
2248+
if (argtypeid == TEXTOID)
2249+
{
2250+
value_str = text_to_cstring(PG_GETARG_TEXT_P(0));
2251+
}
2252+
else
2253+
{
2254+
Oid typoutput;
2255+
bool typisvarlena;
2256+
getTypeOutputInfo(argtypeid, &typoutput, &typisvarlena);
2257+
value_str = OidOutputFunctionCall(typoutput, PG_GETARG_DATUM(0));
2258+
}
2259+
2260+
/* Try to perform the conversion to numeric. */
2261+
result = InputFunctionCallSafe(&my_extra->numeric_inputproc,
2262+
value_str,
2263+
my_extra->numeric_typioparam,
2264+
-1,
2265+
(Node *) &numeric_escontext,
2266+
&converted);
2267+
2268+
/* If conversion to numeric fails, try to perform the conversion to money. */
2269+
if (!result)
2270+
{
2271+
result = InputFunctionCallSafe(&my_extra->money_inputproc,
2272+
value_str,
2273+
my_extra->money_typioparam,
2274+
-1,
2275+
(Node *) &money_escontext,
2276+
&converted);
2277+
}
2278+
2279+
pfree(value_str);
2280+
PG_RETURN_INT32(result ? 1 : 0);
2281+
}
2282+
2283+
2284+
21522285
/*
21532286
* object_id
21542287
* Returns the object ID with object name and object type as input where object type is optional

0 commit comments

Comments
 (0)