summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDean Rasheed2023-02-04 09:48:51 +0000
committerDean Rasheed2023-02-04 09:48:51 +0000
commitfaff8f8e47f18c7d589453e2e0d841d2bd96c1ac (patch)
tree84c64f4f9cb6e7713d955f8b3193ff84b42c8cee
parent1b6f632a35f8715f8c64e7930adebc7f1d292074 (diff)
Allow underscores in integer and numeric constants.
This allows underscores to be used in integer and numeric literals, and their corresponding type input functions, for visual grouping. For example: 1_500_000_000 3.14159_26535_89793 0xffff_ffff 0b_1001_0001 A single underscore is allowed between any 2 digits, or immediately after the base prefix indicator of non-decimal integers, per SQL:202x draft. Peter Eisentraut and Dean Rasheed Discussion: https://postgr.es/m/84aae844-dc55-a4be-86d9-4f0fa405cc97%40enterprisedb.com
-rw-r--r--doc/src/sgml/syntax.sgml36
-rw-r--r--src/backend/catalog/sql_features.txt1
-rw-r--r--src/backend/parser/parse_node.c43
-rw-r--r--src/backend/parser/scan.l27
-rw-r--r--src/backend/utils/adt/numeric.c106
-rw-r--r--src/backend/utils/adt/numutils.c273
-rw-r--r--src/fe_utils/psqlscan.l16
-rw-r--r--src/interfaces/ecpg/preproc/pgc.l16
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_trap.out2
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_trap.sql2
-rw-r--r--src/test/regress/expected/int2.out44
-rw-r--r--src/test/regress/expected/int4.out44
-rw-r--r--src/test/regress/expected/int8.out44
-rw-r--r--src/test/regress/expected/numeric.out62
-rw-r--r--src/test/regress/expected/numerology.out92
-rw-r--r--src/test/regress/expected/partition_prune.out6
-rw-r--r--src/test/regress/sql/int2.sql14
-rw-r--r--src/test/regress/sql/int4.sql14
-rw-r--r--src/test/regress/sql/int8.sql14
-rw-r--r--src/test/regress/sql/numeric.sql22
-rw-r--r--src/test/regress/sql/numerology.sql24
-rw-r--r--src/test/regress/sql/partition_prune.sql6
22 files changed, 724 insertions, 184 deletions
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 0ccddea310..5668ab0143 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -677,7 +677,8 @@ $function$
decimal point, if one is used. At least one digit must follow the
exponent marker (<literal>e</literal>), if one is present.
There cannot be any spaces or other characters embedded in the
- constant. Note that any leading plus or minus sign is not actually
+ constant, except for underscores, which can be used for visual grouping as
+ described below. Note that any leading plus or minus sign is not actually
considered part of the constant; it is an operator applied to the
constant.
</para>
@@ -695,23 +696,24 @@ $function$
</para>
<para>
- Additionally, non-decimal integer constants can be used in these forms:
+ Additionally, non-decimal integer constants are accepted in these forms:
<synopsis>
0x<replaceable>hexdigits</replaceable>
0o<replaceable>octdigits</replaceable>
0b<replaceable>bindigits</replaceable>
</synopsis>
- <replaceable>hexdigits</replaceable> is one or more hexadecimal digits
+ where <replaceable>hexdigits</replaceable> is one or more hexadecimal digits
(0-9, A-F), <replaceable>octdigits</replaceable> is one or more octal
- digits (0-7), <replaceable>bindigits</replaceable> is one or more binary
+ digits (0-7), and <replaceable>bindigits</replaceable> is one or more binary
digits (0 or 1). Hexadecimal digits and the radix prefixes can be in
upper or lower case. Note that only integers can have non-decimal forms,
not numbers with fractional parts.
</para>
<para>
- These are some examples of this:
-<literallayout>0b100101
+ These are some examples of valid non-decimal integer constants:
+<literallayout>
+0b100101
0B10011001
0o273
0O755
@@ -720,13 +722,21 @@ $function$
</literallayout>
</para>
- <note>
- <para>
- Non-decimal integer constants are currently only supported in the range
- of the <type>bigint</type> type (see <xref
- linkend="datatype-numeric-table"/>).
- </para>
- </note>
+ <para>
+ For visual grouping, underscores can be inserted between digits. These
+ have no further effect on the value of the constant. For example:
+<literallayout>
+1_500_000_000
+0b10001000_00000000
+0o_1_755
+0xFFFF_FFFF
+1.618_034
+</literallayout>
+ Underscores are not allowed at the start or end of a numeric constant or
+ a group of digits (that is, immediately before or after the decimal point
+ or the exponent marker), and more than one underscore in a row is not
+ allowed.
+ </para>
<para>
<indexterm><primary>integer</primary></indexterm>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index abad216b7e..3766762ae3 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -528,6 +528,7 @@ T653 SQL-schema statements in external routines YES
T654 SQL-dynamic statements in external routines NO
T655 Cyclically dependent routines YES
T661 Non-decimal integer literals YES SQL:202x draft
+T662 Underscores in integer literals YES SQL:202x draft
T811 Basic SQL/JSON constructor functions NO
T812 SQL/JSON: JSON_OBJECTAGG NO
T813 SQL/JSON: JSON_ARRAYAGG with ORDER BY NO
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f1967a33bc..5020b9f081 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -19,6 +19,7 @@
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
#include "nodes/nodeFuncs.h"
#include "nodes/subscripting.h"
#include "parser/parse_coerce.h"
@@ -385,47 +386,11 @@ make_const(ParseState *pstate, A_Const *aconst)
{
/* could be an oversize integer as well as a float ... */
- int base = 10;
- char *startptr;
- int sign;
- char *testvalue;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
int64 val64;
- char *endptr;
- startptr = aconst->val.fval.fval;
- if (startptr[0] == '-')
- {
- sign = -1;
- startptr++;
- }
- else
- sign = +1;
- if (startptr[0] == '0')
- {
- if (startptr[1] == 'b' || startptr[1] == 'B')
- {
- base = 2;
- startptr += 2;
- }
- else if (startptr[1] == 'o' || startptr[1] == 'O')
- {
- base = 8;
- startptr += 2;
- }
- else if (startptr[1] == 'x' || startptr[1] == 'X')
- {
- base = 16;
- startptr += 2;
- }
- }
-
- if (sign == +1)
- testvalue = startptr;
- else
- testvalue = psprintf("-%s", startptr);
- errno = 0;
- val64 = strtoi64(testvalue, &endptr, base);
- if (errno == 0 && *endptr == '\0')
+ val64 = pg_strtoint64_safe(aconst->val.fval.fval, (Node *) &escontext);
+ if (!escontext.error_occurred)
{
/*
* It might actually fit in int32. Probably only INT_MIN
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 1e821d4c9e..b2216a9eac 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -37,10 +37,12 @@
#include "common/string.h"
#include "gramparse.h"
+#include "nodes/miscnodes.h"
#include "parser/parser.h" /* only needed for GUC variables */
#include "parser/scansup.h"
#include "port/pg_bitutils.h"
#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
}
%{
@@ -395,19 +397,19 @@ hexdigit [0-9A-Fa-f]
octdigit [0-7]
bindigit [0-1]
-decinteger {decdigit}+
-hexinteger 0[xX]{hexdigit}+
-octinteger 0[oO]{octdigit}+
-bininteger 0[bB]{bindigit}+
+decinteger {decdigit}(_?{decdigit})*
+hexinteger 0[xX](_?{hexdigit})+
+octinteger 0[oO](_?{octdigit})+
+bininteger 0[bB](_?{bindigit})+
-hexfail 0[xX]
-octfail 0[oO]
-binfail 0[bB]
+hexfail 0[xX]_?
+octfail 0[oO]_?
+binfail 0[bB]_?
numeric (({decinteger}\.{decinteger}?)|(\.{decinteger}))
numericfail {decdigit}+\.\.
-real ({decinteger}|{numeric})[Ee][-+]?{decdigit}+
+real ({decinteger}|{numeric})[Ee][-+]?{decinteger}
realfail ({decinteger}|{numeric})[Ee][-+]
decinteger_junk {decinteger}{ident_start}
@@ -1364,12 +1366,11 @@ litbufdup(core_yyscan_t yyscanner)
static int
process_integer_literal(const char *token, YYSTYPE *lval, int base)
{
- int val;
- char *endptr;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+ int32 val;
- errno = 0;
- val = strtoint(base == 10 ? token : token + 2, &endptr, base);
- if (*endptr != '\0' || errno == ERANGE)
+ val = pg_strtoint32_safe(token, (Node *) &escontext);
+ if (escontext.error_occurred)
{
/* integer too large (or contains decimal pt), treat it as a float */
lval->str = pstrdup(token);
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 6bf6db6e27..a83feea396 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -6968,10 +6968,7 @@ set_var_from_str(const char *str, const char *cp,
}
if (!isdigit((unsigned char) *cp))
- ereturn(escontext, false,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ goto invalid_syntax;
decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
@@ -6992,12 +6989,19 @@ set_var_from_str(const char *str, const char *cp,
else if (*cp == '.')
{
if (have_dp)
- ereturn(escontext, false,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
+ goto invalid_syntax;
have_dp = true;
cp++;
+ /* decimal point must not be followed by underscore */
+ if (*cp == '_')
+ goto invalid_syntax;
+ }
+ else if (*cp == '_')
+ {
+ /* underscore must be followed by more digits */
+ cp++;
+ if (!isdigit((unsigned char) *cp))
+ goto invalid_syntax;
}
else
break;
@@ -7010,17 +7014,8 @@ set_var_from_str(const char *str, const char *cp,
/* Handle exponent, if any */
if (*cp == 'e' || *cp == 'E')
{
- long exponent;
- char *endptr;
-
- cp++;
- exponent = strtol(cp, &endptr, 10);
- if (endptr == cp)
- ereturn(escontext, false,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
- cp = endptr;
+ int64 exponent = 0;
+ bool neg = false;
/*
* At this point, dweight and dscale can't be more than about
@@ -7030,10 +7025,43 @@ set_var_from_str(const char *str, const char *cp,
* fit in storage format, make_result() will complain about it later;
* for consistency use the same ereport errcode/text as make_result().
*/
- if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
- ereturn(escontext, false,
- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- errmsg("value overflows numeric format")));
+
+ /* exponent sign */
+ cp++;
+ if (*cp == '+')
+ cp++;
+ else if (*cp == '-')
+ {
+ neg = true;
+ cp++;
+ }
+
+ /* exponent digits */
+ if (!isdigit((unsigned char) *cp))
+ goto invalid_syntax;
+
+ while (*cp)
+ {
+ if (isdigit((unsigned char) *cp))
+ {
+ exponent = exponent * 10 + (*cp++ - '0');
+ if (exponent > PG_INT32_MAX / 2)
+ goto out_of_range;
+ }
+ else if (*cp == '_')
+ {
+ /* underscore must be followed by more digits */
+ cp++;
+ if (!isdigit((unsigned char) *cp))
+ goto invalid_syntax;
+ }
+ else
+ break;
+ }
+
+ if (neg)
+ exponent = -exponent;
+
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
@@ -7085,6 +7113,17 @@ set_var_from_str(const char *str, const char *cp,
*endptr = cp;
return true;
+
+out_of_range:
+ ereturn(escontext, false,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format")));
+
+invalid_syntax:
+ ereturn(escontext, false,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str)));
}
@@ -7167,6 +7206,13 @@ set_var_from_non_decimal_integer_str(const char *str, const char *cp, int sign,
tmp = tmp * 16 + xdigit_value(*cp++);
mul = mul * 16;
}
+ else if (*cp == '_')
+ {
+ /* Underscore must be followed by more digits */
+ cp++;
+ if (!isxdigit((unsigned char) *cp))
+ goto invalid_syntax;
+ }
else
break;
}
@@ -7197,6 +7243,13 @@ set_var_from_non_decimal_integer_str(const char *str, const char *cp, int sign,
tmp = tmp * 8 + (*cp++ - '0');
mul = mul * 8;
}
+ else if (*cp == '_')
+ {
+ /* Underscore must be followed by more digits */
+ cp++;
+ if (*cp < '0' || *cp > '7')
+ goto invalid_syntax;
+ }
else
break;
}
@@ -7227,6 +7280,13 @@ set_var_from_non_decimal_integer_str(const char *str, const char *cp, int sign,
tmp = tmp * 2 + (*cp++ - '0');
mul = mul * 2;
}
+ else if (*cp == '_')
+ {
+ /* Underscore must be followed by more digits */
+ cp++;
+ if (*cp < '0' || *cp > '1')
+ goto invalid_syntax;
+ }
else
break;
}
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index b0e412e7c6..471fbb7ee6 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -141,48 +141,99 @@ pg_strtoint16_safe(const char *s, Node *escontext)
{
firstdigit = ptr += 2;
- while (*ptr && isxdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT16_MIN / 16)))
- goto out_of_range;
-
- tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ if (isxdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT16_MIN / 16)))
+ goto out_of_range;
+
+ tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isxdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '7'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT16_MIN / 8)))
- goto out_of_range;
-
- tmp = tmp * 8 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '7')
+ {
+ if (unlikely(tmp > -(PG_INT16_MIN / 8)))
+ goto out_of_range;
+
+ tmp = tmp * 8 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '7')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '1'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT16_MIN / 2)))
- goto out_of_range;
-
- tmp = tmp * 2 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '1')
+ {
+ if (unlikely(tmp > -(PG_INT16_MIN / 2)))
+ goto out_of_range;
+
+ tmp = tmp * 2 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '1')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else
{
firstdigit = ptr;
- while (*ptr && isdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT16_MIN / 10)))
- goto out_of_range;
-
- tmp = tmp * 10 + (*ptr++ - '0');
+ if (isdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT16_MIN / 10)))
+ goto out_of_range;
+
+ tmp = tmp * 10 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore may not be first */
+ if (unlikely(ptr == firstdigit))
+ goto invalid_syntax;
+ /* and it must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
@@ -268,48 +319,99 @@ pg_strtoint32_safe(const char *s, Node *escontext)
{
firstdigit = ptr += 2;
- while (*ptr && isxdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT32_MIN / 16)))
- goto out_of_range;
-
- tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ if (isxdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT32_MIN / 16)))
+ goto out_of_range;
+
+ tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isxdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '7'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT32_MIN / 8)))
- goto out_of_range;
-
- tmp = tmp * 8 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '7')
+ {
+ if (unlikely(tmp > -(PG_INT32_MIN / 8)))
+ goto out_of_range;
+
+ tmp = tmp * 8 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '7')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '1'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT32_MIN / 2)))
- goto out_of_range;
-
- tmp = tmp * 2 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '1')
+ {
+ if (unlikely(tmp > -(PG_INT32_MIN / 2)))
+ goto out_of_range;
+
+ tmp = tmp * 2 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '1')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else
{
firstdigit = ptr;
- while (*ptr && isdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT32_MIN / 10)))
- goto out_of_range;
-
- tmp = tmp * 10 + (*ptr++ - '0');
+ if (isdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT32_MIN / 10)))
+ goto out_of_range;
+
+ tmp = tmp * 10 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore may not be first */
+ if (unlikely(ptr == firstdigit))
+ goto invalid_syntax;
+ /* and it must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
@@ -395,48 +497,99 @@ pg_strtoint64_safe(const char *s, Node *escontext)
{
firstdigit = ptr += 2;
- while (*ptr && isxdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT64_MIN / 16)))
- goto out_of_range;
-
- tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ if (isxdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT64_MIN / 16)))
+ goto out_of_range;
+
+ tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++];
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isxdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '7'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT64_MIN / 8)))
- goto out_of_range;
-
- tmp = tmp * 8 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '7')
+ {
+ if (unlikely(tmp > -(PG_INT64_MIN / 8)))
+ goto out_of_range;
+
+ tmp = tmp * 8 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '7')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B'))
{
firstdigit = ptr += 2;
- while (*ptr && (*ptr >= '0' && *ptr <= '1'))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT64_MIN / 2)))
- goto out_of_range;
-
- tmp = tmp * 2 + (*ptr++ - '0');
+ if (*ptr >= '0' && *ptr <= '1')
+ {
+ if (unlikely(tmp > -(PG_INT64_MIN / 2)))
+ goto out_of_range;
+
+ tmp = tmp * 2 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || *ptr < '0' || *ptr > '1')
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
else
{
firstdigit = ptr;
- while (*ptr && isdigit((unsigned char) *ptr))
+ while (*ptr)
{
- if (unlikely(tmp > -(PG_INT64_MIN / 10)))
- goto out_of_range;
-
- tmp = tmp * 10 + (*ptr++ - '0');
+ if (isdigit((unsigned char) *ptr))
+ {
+ if (unlikely(tmp > -(PG_INT64_MIN / 10)))
+ goto out_of_range;
+
+ tmp = tmp * 10 + (*ptr++ - '0');
+ }
+ else if (*ptr == '_')
+ {
+ /* underscore may not be first */
+ if (unlikely(ptr == firstdigit))
+ goto invalid_syntax;
+ /* and it must be followed by more digits */
+ ptr++;
+ if (*ptr == '\0' || !isdigit((unsigned char) *ptr))
+ goto invalid_syntax;
+ }
+ else
+ break;
}
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index bec5095739..84754aca4a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -333,19 +333,19 @@ hexdigit [0-9A-Fa-f]
octdigit [0-7]
bindigit [0-1]
-decinteger {decdigit}+
-hexinteger 0[xX]{hexdigit}+
-octinteger 0[oO]{octdigit}+
-bininteger 0[bB]{bindigit}+
+decinteger {decdigit}(_?{decdigit})*
+hexinteger 0[xX](_?{hexdigit})+
+octinteger 0[oO](_?{octdigit})+
+bininteger 0[bB](_?{bindigit})+
-hexfail 0[xX]
-octfail 0[oO]
-binfail 0[bB]
+hexfail 0[xX]_?
+octfail 0[oO]_?
+binfail 0[bB]_?
numeric (({decinteger}\.{decinteger}?)|(\.{decinteger}))
numericfail {decdigit}+\.\.
-real ({decinteger}|{numeric})[Ee][-+]?{decdigit}+
+real ({decinteger}|{numeric})[Ee][-+]?{decinteger}
realfail ({decinteger}|{numeric})[Ee][-+]
decinteger_junk {decinteger}{ident_start}
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index 75815bddb7..dcd567e8c3 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -361,19 +361,19 @@ hexdigit [0-9A-Fa-f]
octdigit [0-7]
bindigit [0-1]
-decinteger {decdigit}+
-hexinteger 0[xX]{hexdigit}+
-octinteger 0[oO]{octdigit}+
-bininteger 0[bB]{bindigit}+
+decinteger {decdigit}(_?{decdigit})*
+hexinteger 0[xX](_?{hexdigit})+
+octinteger 0[oO](_?{octdigit})+
+bininteger 0[bB](_?{bindigit})+
-hexfail 0[xX]
-octfail 0[oO]
-binfail 0[bB]
+hexfail 0[xX]_?
+octfail 0[oO]_?
+binfail 0[bB]_?
numeric (({decinteger}\.{decinteger}?)|(\.{decinteger}))
numericfail {decdigit}+\.\.
-real ({decinteger}|{numeric})[Ee][-+]?{decdigit}+
+real ({decinteger}|{numeric})[Ee][-+]?{decinteger}
realfail ({decinteger}|{numeric})[Ee][-+]
decinteger_junk {decinteger}{ident_start}
diff --git a/src/pl/plpgsql/src/expected/plpgsql_trap.out b/src/pl/plpgsql/src/expected/plpgsql_trap.out
index 90cf6c2895..62d1679c28 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_trap.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_trap.out
@@ -141,7 +141,7 @@ begin
declare x int;
begin
-- we assume this will take longer than 1 second:
- select count(*) into x from generate_series(1, 1000000000000);
+ select count(*) into x from generate_series(1, 1_000_000_000_000);
exception
when others then
raise notice 'caught others?';
diff --git a/src/pl/plpgsql/src/sql/plpgsql_trap.sql b/src/pl/plpgsql/src/sql/plpgsql_trap.sql
index c6c1ad894b..5459b347e7 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_trap.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_trap.sql
@@ -88,7 +88,7 @@ begin
declare x int;
begin
-- we assume this will take longer than 1 second:
- select count(*) into x from generate_series(1, 1000000000000);
+ select count(*) into x from generate_series(1, 1_000_000_000_000);
exception
when others then
raise notice 'caught others?';
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 08c333b75a..73b4ee023c 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -440,3 +440,47 @@ SELECT int2 '-0x8001';
ERROR: value "-0x8001" is out of range for type smallint
LINE 1: SELECT int2 '-0x8001';
^
+-- underscores
+SELECT int2 '1_000';
+ int2
+------
+ 1000
+(1 row)
+
+SELECT int2 '1_2_3';
+ int2
+------
+ 123
+(1 row)
+
+SELECT int2 '0xE_FF';
+ int2
+------
+ 3839
+(1 row)
+
+SELECT int2 '0o2_73';
+ int2
+------
+ 187
+(1 row)
+
+SELECT int2 '0b_10_0101';
+ int2
+------
+ 37
+(1 row)
+
+-- error cases
+SELECT int2 '_100';
+ERROR: invalid input syntax for type smallint: "_100"
+LINE 1: SELECT int2 '_100';
+ ^
+SELECT int2 '100_';
+ERROR: invalid input syntax for type smallint: "100_"
+LINE 1: SELECT int2 '100_';
+ ^
+SELECT int2 '10__000';
+ERROR: invalid input syntax for type smallint: "10__000"
+LINE 1: SELECT int2 '10__000';
+ ^
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index 8386c7cdff..9c20574ca5 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -548,3 +548,47 @@ SELECT int4 '-0x80000001';
ERROR: value "-0x80000001" is out of range for type integer
LINE 1: SELECT int4 '-0x80000001';
^
+-- underscores
+SELECT int4 '1_000_000';
+ int4
+---------
+ 1000000
+(1 row)
+
+SELECT int4 '1_2_3';
+ int4
+------
+ 123
+(1 row)
+
+SELECT int4 '0x1EEE_FFFF';
+ int4
+-----------
+ 518979583
+(1 row)
+
+SELECT int4 '0o2_73';
+ int4
+------
+ 187
+(1 row)
+
+SELECT int4 '0b_10_0101';
+ int4
+------
+ 37
+(1 row)
+
+-- error cases
+SELECT int4 '_100';
+ERROR: invalid input syntax for type integer: "_100"
+LINE 1: SELECT int4 '_100';
+ ^
+SELECT int4 '100_';
+ERROR: invalid input syntax for type integer: "100_"
+LINE 1: SELECT int4 '100_';
+ ^
+SELECT int4 '100__000';
+ERROR: invalid input syntax for type integer: "100__000"
+LINE 1: SELECT int4 '100__000';
+ ^
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 5b62b51be9..d9dca64e88 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -1044,3 +1044,47 @@ SELECT int8 '-0x8000000000000001';
ERROR: value "-0x8000000000000001" is out of range for type bigint
LINE 1: SELECT int8 '-0x8000000000000001';
^
+-- underscores
+SELECT int8 '1_000_000';
+ int8
+---------
+ 1000000
+(1 row)
+
+SELECT int8 '1_2_3';
+ int8
+------
+ 123
+(1 row)
+
+SELECT int8 '0x1EEE_FFFF';
+ int8
+-----------
+ 518979583
+(1 row)
+
+SELECT int8 '0o2_73';
+ int8
+------
+ 187
+(1 row)
+
+SELECT int8 '0b_10_0101';
+ int8
+------
+ 37
+(1 row)
+
+-- error cases
+SELECT int8 '_100';
+ERROR: invalid input syntax for type bigint: "_100"
+LINE 1: SELECT int8 '_100';
+ ^
+SELECT int8 '100_';
+ERROR: invalid input syntax for type bigint: "100_"
+LINE 1: SELECT int8 '100_';
+ ^
+SELECT int8 '100__000';
+ERROR: invalid input syntax for type bigint: "100__000"
+LINE 1: SELECT int8 '100__000';
+ ^
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 94796522d9..56a3f3630a 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2144,12 +2144,17 @@ INSERT INTO num_input_test(n1) VALUES (' -inf ');
INSERT INTO num_input_test(n1) VALUES (' Infinity ');
INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
+INSERT INTO num_input_test(n1) VALUES ('12_000_000_000');
+INSERT INTO num_input_test(n1) VALUES ('12_000.123_456');
+INSERT INTO num_input_test(n1) VALUES ('23_000_000_000e-1_0');
+INSERT INTO num_input_test(n1) VALUES ('.000_000_000_123e1_0');
+INSERT INTO num_input_test(n1) VALUES ('.000_000_000_123e+1_1');
INSERT INTO num_input_test(n1) VALUES ('0b10001110111100111100001001010');
-INSERT INTO num_input_test(n1) VALUES (' -0B1010101101010100101010011000110011101011000111110000101011010010 ');
+INSERT INTO num_input_test(n1) VALUES (' -0B_1010_1011_0101_0100_1010_1001_1000_1100_1110_1011_0001_1111_0000_1010_1101_0010 ');
INSERT INTO num_input_test(n1) VALUES (' +0o112402761777 ');
-INSERT INTO num_input_test(n1) VALUES ('-0O001255245230633431670261');
+INSERT INTO num_input_test(n1) VALUES ('-0O0012_5524_5230_6334_3167_0261');
INSERT INTO num_input_test(n1) VALUES ('-0x0000000000000000000000000deadbeef');
-INSERT INTO num_input_test(n1) VALUES (' 0X30b1F33a6DF0bD4E64DF9BdA7D15 ');
+INSERT INTO num_input_test(n1) VALUES (' 0X_30b1_F33a_6DF0_bD4E_64DF_9BdA_7D15 ');
-- bad inputs
INSERT INTO num_input_test(n1) VALUES (' ');
ERROR: invalid input syntax for type numeric: " "
@@ -2195,6 +2200,38 @@ INSERT INTO num_input_test(n1) VALUES ('+ infinity');
ERROR: invalid input syntax for type numeric: "+ infinity"
LINE 1: INSERT INTO num_input_test(n1) VALUES ('+ infinity');
^
+INSERT INTO num_input_test(n1) VALUES ('_123');
+ERROR: invalid input syntax for type numeric: "_123"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('_123');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('123_');
+ERROR: invalid input syntax for type numeric: "123_"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('123_');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('12__34');
+ERROR: invalid input syntax for type numeric: "12__34"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('12__34');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('123_.456');
+ERROR: invalid input syntax for type numeric: "123_.456"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('123_.456');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('123._456');
+ERROR: invalid input syntax for type numeric: "123._456"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('123._456');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('1.2e_34');
+ERROR: invalid input syntax for type numeric: "1.2e_34"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('1.2e_34');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('1.2e34_');
+ERROR: invalid input syntax for type numeric: "1.2e34_"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('1.2e34_');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('1.2e3__4');
+ERROR: invalid input syntax for type numeric: "1.2e3__4"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('1.2e3__4');
+ ^
INSERT INTO num_input_test(n1) VALUES ('0b1112');
ERROR: invalid input syntax for type numeric: "0b1112"
LINE 1: INSERT INTO num_input_test(n1) VALUES ('0b1112');
@@ -2215,6 +2252,18 @@ INSERT INTO num_input_test(n1) VALUES ('0x12.34');
ERROR: invalid input syntax for type numeric: "0x12.34"
LINE 1: INSERT INTO num_input_test(n1) VALUES ('0x12.34');
^
+INSERT INTO num_input_test(n1) VALUES ('0x__1234');
+ERROR: invalid input syntax for type numeric: "0x__1234"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('0x__1234');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('0x1234_');
+ERROR: invalid input syntax for type numeric: "0x1234_"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('0x1234_');
+ ^
+INSERT INTO num_input_test(n1) VALUES ('0x12__34');
+ERROR: invalid input syntax for type numeric: "0x12__34"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('0x12__34');
+ ^
SELECT * FROM num_input_test;
n1
-----------------------------------
@@ -2231,13 +2280,18 @@ SELECT * FROM num_input_test;
Infinity
Infinity
-Infinity
+ 12000000000
+ 12000.123456
+ 2.3000000000
+ 1.23
+ 12.3
299792458
-12345678901234567890
9999999999
-12345678900987654321
-3735928559
987654321234567898765432123456789
-(19 rows)
+(24 rows)
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('34.5', 'numeric');
diff --git a/src/test/regress/expected/numerology.out b/src/test/regress/expected/numerology.out
index deb26d31c3..f662a5050a 100644
--- a/src/test/regress/expected/numerology.out
+++ b/src/test/regress/expected/numerology.out
@@ -178,10 +178,6 @@ SELECT 0x0o;
ERROR: trailing junk after numeric literal at or near "0x0o"
LINE 1: SELECT 0x0o;
^
-SELECT 1_2_3;
-ERROR: trailing junk after numeric literal at or near "1_"
-LINE 1: SELECT 1_2_3;
- ^
SELECT 0.a;
ERROR: trailing junk after numeric literal at or near "0.a"
LINE 1: SELECT 0.a;
@@ -246,6 +242,94 @@ SELECT 0x0y;
ERROR: trailing junk after numeric literal at or near "0x0y"
LINE 1: SELECT 0x0y;
^
+-- underscores
+SELECT 1_000_000;
+ ?column?
+----------
+ 1000000
+(1 row)
+
+SELECT 1_2_3;
+ ?column?
+----------
+ 123
+(1 row)
+
+SELECT 0x1EEE_FFFF;
+ ?column?
+-----------
+ 518979583
+(1 row)
+
+SELECT 0o2_73;
+ ?column?
+----------
+ 187
+(1 row)
+
+SELECT 0b_10_0101;
+ ?column?
+----------
+ 37
+(1 row)
+
+SELECT 1_000.000_005;
+ ?column?
+-------------
+ 1000.000005
+(1 row)
+
+SELECT 1_000.;
+ ?column?
+----------
+ 1000
+(1 row)
+
+SELECT .000_005;
+ ?column?
+----------
+ 0.000005
+(1 row)
+
+SELECT 1_000.5e0_1;
+ ?column?
+----------
+ 10005
+(1 row)
+
+-- error cases
+SELECT _100;
+ERROR: column "_100" does not exist
+LINE 1: SELECT _100;
+ ^
+SELECT 100_;
+ERROR: trailing junk after numeric literal at or near "100_"
+LINE 1: SELECT 100_;
+ ^
+SELECT 100__000;
+ERROR: trailing junk after numeric literal at or near "100_"
+LINE 1: SELECT 100__000;
+ ^
+SELECT _1_000.5;
+ERROR: syntax error at or near ".5"
+LINE 1: SELECT _1_000.5;
+ ^
+SELECT 1_000_.5;
+ERROR: trailing junk after numeric literal at or near "1_000_"
+LINE 1: SELECT 1_000_.5;
+ ^
+SELECT 1_000._5;
+ERROR: trailing junk after numeric literal at or near "1_000._"
+LINE 1: SELECT 1_000._5;
+ ^
+SELECT 1_000.5_;
+ERROR: trailing junk after numeric literal at or near "1_000.5_"
+LINE 1: SELECT 1_000.5_;
+ ^
+SELECT 1_000.5e_1;
+ERROR: trailing junk after numeric literal at or near "1_000.5e"
+LINE 1: SELECT 1_000.5e_1;
+ ^
--
-- Test implicit type conversions
-- This fails for Postgres v6.1 (and earlier?)
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 7555764c77..d700c00629 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1503,7 +1503,7 @@ explain (costs off) select * from like_op_noprune where a like '%BC';
create table lparted_by_int2 (a smallint) partition by list (a);
create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1);
create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384);
-explain (costs off) select * from lparted_by_int2 where a = 100000000000000;
+explain (costs off) select * from lparted_by_int2 where a = 100_000_000_000_000;
QUERY PLAN
--------------------------
Result
@@ -1514,7 +1514,7 @@ create table rparted_by_int2 (a smallint) partition by range (a);
create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10);
create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384);
-- all partitions pruned
-explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
+explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000;
QUERY PLAN
--------------------------
Result
@@ -1523,7 +1523,7 @@ explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue);
-- all partitions but rparted_by_int2_maxvalue pruned
-explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
+explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000;
QUERY PLAN
------------------------------------------------------
Seq Scan on rparted_by_int2_maxvalue rparted_by_int2
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index a812235ee5..ce8ac97963 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -141,3 +141,17 @@ SELECT int2 '-0o100000';
SELECT int2 '-0o100001';
SELECT int2 '-0x8000';
SELECT int2 '-0x8001';
+
+
+-- underscores
+
+SELECT int2 '1_000';
+SELECT int2 '1_2_3';
+SELECT int2 '0xE_FF';
+SELECT int2 '0o2_73';
+SELECT int2 '0b_10_0101';
+
+-- error cases
+SELECT int2 '_100';
+SELECT int2 '100_';
+SELECT int2 '10__000';
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index 9e6a40408a..146963edfb 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -196,3 +196,17 @@ SELECT int4 '-0o20000000000';
SELECT int4 '-0o20000000001';
SELECT int4 '-0x80000000';
SELECT int4 '-0x80000001';
+
+
+-- underscores
+
+SELECT int4 '1_000_000';
+SELECT int4 '1_2_3';
+SELECT int4 '0x1EEE_FFFF';
+SELECT int4 '0o2_73';
+SELECT int4 '0b_10_0101';
+
+-- error cases
+SELECT int4 '_100';
+SELECT int4 '100_';
+SELECT int4 '100__000';
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index 06f273ed58..c85717c072 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -277,3 +277,17 @@ SELECT int8 '-0o1000000000000000000000';
SELECT int8 '-0o1000000000000000000001';
SELECT int8 '-0x8000000000000000';
SELECT int8 '-0x8000000000000001';
+
+
+-- underscores
+
+SELECT int8 '1_000_000';
+SELECT int8 '1_2_3';
+SELECT int8 '0x1EEE_FFFF';
+SELECT int8 '0o2_73';
+SELECT int8 '0b_10_0101';
+
+-- error cases
+SELECT int8 '_100';
+SELECT int8 '100_';
+SELECT int8 '100__000';
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index fe93714dd1..2db7656e84 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1039,12 +1039,17 @@ INSERT INTO num_input_test(n1) VALUES (' -inf ');
INSERT INTO num_input_test(n1) VALUES (' Infinity ');
INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
+INSERT INTO num_input_test(n1) VALUES ('12_000_000_000');
+INSERT INTO num_input_test(n1) VALUES ('12_000.123_456');
+INSERT INTO num_input_test(n1) VALUES ('23_000_000_000e-1_0');
+INSERT INTO num_input_test(n1) VALUES ('.000_000_000_123e1_0');
+INSERT INTO num_input_test(n1) VALUES ('.000_000_000_123e+1_1');
INSERT INTO num_input_test(n1) VALUES ('0b10001110111100111100001001010');
-INSERT INTO num_input_test(n1) VALUES (' -0B1010101101010100101010011000110011101011000111110000101011010010 ');
+INSERT INTO num_input_test(n1) VALUES (' -0B_1010_1011_0101_0100_1010_1001_1000_1100_1110_1011_0001_1111_0000_1010_1101_0010 ');
INSERT INTO num_input_test(n1) VALUES (' +0o112402761777 ');
-INSERT INTO num_input_test(n1) VALUES ('-0O001255245230633431670261');
+INSERT INTO num_input_test(n1) VALUES ('-0O0012_5524_5230_6334_3167_0261');
INSERT INTO num_input_test(n1) VALUES ('-0x0000000000000000000000000deadbeef');
-INSERT INTO num_input_test(n1) VALUES (' 0X30b1F33a6DF0bD4E64DF9BdA7D15 ');
+INSERT INTO num_input_test(n1) VALUES (' 0X_30b1_F33a_6DF0_bD4E_64DF_9BdA_7D15 ');
-- bad inputs
INSERT INTO num_input_test(n1) VALUES (' ');
@@ -1058,11 +1063,22 @@ INSERT INTO num_input_test(n1) VALUES (' N aN ');
INSERT INTO num_input_test(n1) VALUES ('+NaN');
INSERT INTO num_input_test(n1) VALUES ('-NaN');
INSERT INTO num_input_test(n1) VALUES ('+ infinity');
+INSERT INTO num_input_test(n1) VALUES ('_123');
+INSERT INTO num_input_test(n1) VALUES ('123_');
+INSERT INTO num_input_test(n1) VALUES ('12__34');
+INSERT INTO num_input_test(n1) VALUES ('123_.456');
+INSERT INTO num_input_test(n1) VALUES ('123._456');
+INSERT INTO num_input_test(n1) VALUES ('1.2e_34');
+INSERT INTO num_input_test(n1) VALUES ('1.2e34_');
+INSERT INTO num_input_test(n1) VALUES ('1.2e3__4');
INSERT INTO num_input_test(n1) VALUES ('0b1112');
INSERT INTO num_input_test(n1) VALUES ('0c1112');
INSERT INTO num_input_test(n1) VALUES ('0o12345678');
INSERT INTO num_input_test(n1) VALUES ('0x1eg');
INSERT INTO num_input_test(n1) VALUES ('0x12.34');
+INSERT INTO num_input_test(n1) VALUES ('0x__1234');
+INSERT INTO num_input_test(n1) VALUES ('0x1234_');
+INSERT INTO num_input_test(n1) VALUES ('0x12__34');
SELECT * FROM num_input_test;
diff --git a/src/test/regress/sql/numerology.sql b/src/test/regress/sql/numerology.sql
index 310d9e5766..1941c58e68 100644
--- a/src/test/regress/sql/numerology.sql
+++ b/src/test/regress/sql/numerology.sql
@@ -45,7 +45,6 @@ SELECT -0x8000000000000001;
-- error cases
SELECT 123abc;
SELECT 0x0o;
-SELECT 1_2_3;
SELECT 0.a;
SELECT 0.0a;
SELECT .0a;
@@ -66,6 +65,29 @@ SELECT 0x;
SELECT 1x;
SELECT 0x0y;
+-- underscores
+SELECT 1_000_000;
+SELECT 1_2_3;
+SELECT 0x1EEE_FFFF;
+SELECT 0o2_73;
+SELECT 0b_10_0101;
+
+SELECT 1_000.000_005;
+SELECT 1_000.;
+SELECT .000_005;
+SELECT 1_000.5e0_1;
+
+-- error cases
+SELECT _100;
+SELECT 100_;
+SELECT 100__000;
+
+SELECT _1_000.5;
+SELECT 1_000_.5;
+SELECT 1_000._5;
+SELECT 1_000.5_;
+SELECT 1_000.5e_1;
+
--
-- Test implicit type conversions
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index d70bd8610c..fb0583f924 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -283,16 +283,16 @@ explain (costs off) select * from like_op_noprune where a like '%BC';
create table lparted_by_int2 (a smallint) partition by list (a);
create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1);
create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384);
-explain (costs off) select * from lparted_by_int2 where a = 100000000000000;
+explain (costs off) select * from lparted_by_int2 where a = 100_000_000_000_000;
create table rparted_by_int2 (a smallint) partition by range (a);
create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10);
create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384);
-- all partitions pruned
-explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
+explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000;
create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue);
-- all partitions but rparted_by_int2_maxvalue pruned
-explain (costs off) select * from rparted_by_int2 where a > 100000000000000;
+explain (costs off) select * from rparted_by_int2 where a > 100_000_000_000_000;
drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2;