Commit 50861cd6 authored by Tom Lane's avatar Tom Lane

Improve portability of I/O behavior for the geometric types.

Formerly, the geometric I/O routines such as box_in and point_out relied
directly on strtod() and sprintf() for conversion of the float8 component
values of their data types.  However, the behavior of those functions is
pretty platform-dependent, especially for edge-case values such as
infinities and NaNs.  This was exposed by commit acdf2a8b, which
added test cases involving boxes with infinity endpoints, and immediately
failed on Windows and AIX buildfarm members.  We solved these problems
years ago in the main float8in and float8out functions, so let's fix it
by making the geometric types use that code instead of depending directly
on the platform-supplied functions.

To do this, refactor the float8in code so that it can be used to parse
just part of a string, and as a convenience make the guts of float8out
usable without going through DirectFunctionCall.

While at it, get rid of geo_ops.c's fairly shaky assumptions about the
maximum output string length for a double, by having it build results in
StringInfo buffers instead of fixed-length strings.

In passing, convert all the "invalid input syntax for type foo" messages
in this area of the code into "invalid input syntax for type %s" to reduce
the number of distinct translatable strings, per recent discussion.
We would have needed a fair number of the latter anyway for code-sharing
reasons, so we might as well just go whole hog.

Note: this patch is by no means intended to guarantee that the geometric
types uniformly behave sanely for infinity or NaN component values.
But any bugs we have in that line were there all along, they were just
harder to reach in a platform-independent way.
parent 818e5937
......@@ -411,17 +411,35 @@ Datum
float8in(PG_FUNCTION_ARGS)
{
char *num = PG_GETARG_CSTRING(0);
char *orig_num;
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
}
/*
* float8in_internal - guts of float8in()
*
* This is exposed for use by functions that want a reasonably
* platform-independent way of inputting doubles. The behavior is
* essentially like strtod + ereport on error, but note the following
* differences:
* 1. Both leading and trailing whitespace are skipped.
* 2. If endptr_p is NULL, we throw error if there's trailing junk.
* Otherwise, it's up to the caller to complain about trailing junk.
* 3. In event of a syntax error, the report mentions the given type_name
* and prints orig_string as the input; this is meant to support use of
* this function with types such as "box" and "point", where what we are
* parsing here is just a substring of orig_string.
*
* "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.
*/
double
float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string)
{
double val;
char *endptr;
/*
* endptr points to the first character _after_ the sequence we recognized
* as a valid floating point number. orig_num points to the original input
* string.
*/
orig_num = num;
/* skip leading whitespace */
while (*num != '\0' && isspace((unsigned char) *num))
num++;
......@@ -433,8 +451,8 @@ float8in(PG_FUNCTION_ARGS)
if (*num == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type double precision: \"%s\"",
orig_num)));
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
errno = 0;
val = strtod(num, &endptr);
......@@ -497,18 +515,27 @@ float8in(PG_FUNCTION_ARGS)
* precision). We'd prefer not to throw error for that, so try to
* detect whether it's a "real" out-of-range condition by checking
* to see if the result is zero or huge.
*
* On error, we intentionally complain about double precision not
* the given type name, and we print only the part of the string
* that is the current number.
*/
if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
{
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",
orig_num)));
errnumber)));
}
}
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type double precision: \"%s\"",
orig_num)));
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
}
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
else
......@@ -527,16 +554,16 @@ float8in(PG_FUNCTION_ARGS)
while (*endptr != '\0' && isspace((unsigned char) *endptr))
endptr++;
/* if there is any junk left at the end of the string, bail out */
if (*endptr != '\0')
/* report stopping point if wanted, else complain if not end of string */
if (endptr_p)
*endptr_p = endptr;
else if (*endptr != '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type double precision: \"%s\"",
orig_num)));
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
CHECKFLOATVAL(val, true, true);
PG_RETURN_FLOAT8(val);
return val;
}
/*
......@@ -547,10 +574,24 @@ Datum
float8out(PG_FUNCTION_ARGS)
{
float8 num = PG_GETARG_FLOAT8(0);
PG_RETURN_CSTRING(float8out_internal(num));
}
/*
* float8out_internal - guts of float8out()
*
* This is exposed for use by functions that want a reasonably
* platform-independent way of outputting doubles.
* The result is always palloc'd.
*/
char *
float8out_internal(double num)
{
char *ascii = (char *) palloc(MAXDOUBLEWIDTH + 1);
if (isnan(num))
PG_RETURN_CSTRING(strcpy(ascii, "NaN"));
return strcpy(ascii, "NaN");
switch (is_infinite(num))
{
......@@ -571,7 +612,7 @@ float8out(PG_FUNCTION_ARGS)
}
}
PG_RETURN_CSTRING(ascii);
return ascii;
}
/*
......
This diff is collapsed.
......@@ -343,6 +343,9 @@ extern float get_float4_infinity(void);
extern double get_float8_nan(void);
extern float get_float4_nan(void);
extern int is_infinite(double val);
extern double float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string);
extern char *float8out_internal(double num);
extern Datum float4in(PG_FUNCTION_ARGS);
extern Datum float4out(PG_FUNCTION_ARGS);
......
......@@ -232,15 +232,15 @@ INSERT INTO box_temp
('(-infinity,-infinity)(infinity,infinity)');
SET enable_seqscan = false;
SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)';
f1
------------------
f1
----------------------------
(2,2),(1,1)
(4,4),(2,2)
(6,6),(3,3)
(8,8),(4,4)
(-0,100),(0,0)
(0,inf),(0,100)
(0,inf),(-inf,0)
(0,Infinity),(0,100)
(0,Infinity),(-Infinity,0)
(7 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)';
......@@ -251,16 +251,16 @@ EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)';
(2 rows)
SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)';
f1
------------------
f1
----------------------------
(2,2),(1,1)
(4,4),(2,2)
(6,6),(3,3)
(8,8),(4,4)
(10,10),(5,5)
(-0,100),(0,0)
(0,inf),(0,100)
(0,inf),(-inf,0)
(0,Infinity),(0,100)
(0,Infinity),(-Infinity,0)
(8 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)';
......@@ -271,8 +271,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)';
(2 rows)
SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)';
f1
-----------------------
f1
-------------------------------------------
(20,20),(10,10)
(22,22),(11,11)
(24,24),(12,12)
......@@ -289,7 +289,7 @@ SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)';
(46,46),(23,23)
(48,48),(24,24)
(50,50),(25,25)
(inf,inf),(-inf,-inf)
(Infinity,Infinity),(-Infinity,-Infinity)
(17 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)';
......@@ -375,10 +375,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)';
(2 rows)
SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)';
f1
-------------------
f1
----------------------
(100,100),(50,50)
(0,inf),(0,100)
(0,Infinity),(0,100)
(2 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)';
......@@ -389,8 +389,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49
(2 rows)
SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
f1
-------------------
f1
----------------------
(82,82),(41,41)
(84,84),(42,42)
(86,86),(43,43)
......@@ -401,7 +401,7 @@ SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
(96,96),(48,48)
(98,98),(49,49)
(100,100),(50,50)
(0,inf),(0,100)
(0,Infinity),(0,100)
(11 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
......@@ -412,12 +412,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)';
(2 rows)
SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,16)';
f1
-----------------------
f1
-------------------------------------------
(16,16),(8,8)
(18,18),(9,9)
(20,20),(10,10)
(inf,inf),(-inf,-inf)
(Infinity,Infinity),(-Infinity,-Infinity)
(4 rows)
EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,15)';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment