Skip to content

Commit 50861cd

Browse files
committed
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 acdf2a8, 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.
1 parent 818e593 commit 50861cd

File tree

4 files changed

+272
-314
lines changed

4 files changed

+272
-314
lines changed

src/backend/utils/adt/float.c

+63-22
Original file line numberDiff line numberDiff line change
@@ -411,17 +411,35 @@ Datum
411411
float8in(PG_FUNCTION_ARGS)
412412
{
413413
char *num = PG_GETARG_CSTRING(0);
414-
char *orig_num;
414+
415+
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
416+
}
417+
418+
/*
419+
* float8in_internal - guts of float8in()
420+
*
421+
* This is exposed for use by functions that want a reasonably
422+
* platform-independent way of inputting doubles. The behavior is
423+
* essentially like strtod + ereport on error, but note the following
424+
* differences:
425+
* 1. Both leading and trailing whitespace are skipped.
426+
* 2. If endptr_p is NULL, we throw error if there's trailing junk.
427+
* Otherwise, it's up to the caller to complain about trailing junk.
428+
* 3. In event of a syntax error, the report mentions the given type_name
429+
* and prints orig_string as the input; this is meant to support use of
430+
* this function with types such as "box" and "point", where what we are
431+
* parsing here is just a substring of orig_string.
432+
*
433+
* "num" could validly be declared "const char *", but that results in an
434+
* unreasonable amount of extra casting both here and in callers, so we don't.
435+
*/
436+
double
437+
float8in_internal(char *num, char **endptr_p,
438+
const char *type_name, const char *orig_string)
439+
{
415440
double val;
416441
char *endptr;
417442

418-
/*
419-
* endptr points to the first character _after_ the sequence we recognized
420-
* as a valid floating point number. orig_num points to the original input
421-
* string.
422-
*/
423-
orig_num = num;
424-
425443
/* skip leading whitespace */
426444
while (*num != '\0' && isspace((unsigned char) *num))
427445
num++;
@@ -433,8 +451,8 @@ float8in(PG_FUNCTION_ARGS)
433451
if (*num == '\0')
434452
ereport(ERROR,
435453
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
436-
errmsg("invalid input syntax for type double precision: \"%s\"",
437-
orig_num)));
454+
errmsg("invalid input syntax for type %s: \"%s\"",
455+
type_name, orig_string)));
438456

439457
errno = 0;
440458
val = strtod(num, &endptr);
@@ -497,18 +515,27 @@ float8in(PG_FUNCTION_ARGS)
497515
* precision). We'd prefer not to throw error for that, so try to
498516
* detect whether it's a "real" out-of-range condition by checking
499517
* to see if the result is zero or huge.
518+
*
519+
* On error, we intentionally complain about double precision not
520+
* the given type name, and we print only the part of the string
521+
* that is the current number.
500522
*/
501523
if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
524+
{
525+
char *errnumber = pstrdup(num);
526+
527+
errnumber[endptr - num] = '\0';
502528
ereport(ERROR,
503529
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
504530
errmsg("\"%s\" is out of range for type double precision",
505-
orig_num)));
531+
errnumber)));
532+
}
506533
}
507534
else
508535
ereport(ERROR,
509536
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
510-
errmsg("invalid input syntax for type double precision: \"%s\"",
511-
orig_num)));
537+
errmsg("invalid input syntax for type %s: \"%s\"",
538+
type_name, orig_string)));
512539
}
513540
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
514541
else
@@ -527,16 +554,16 @@ float8in(PG_FUNCTION_ARGS)
527554
while (*endptr != '\0' && isspace((unsigned char) *endptr))
528555
endptr++;
529556

530-
/* if there is any junk left at the end of the string, bail out */
531-
if (*endptr != '\0')
557+
/* report stopping point if wanted, else complain if not end of string */
558+
if (endptr_p)
559+
*endptr_p = endptr;
560+
else if (*endptr != '\0')
532561
ereport(ERROR,
533562
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
534-
errmsg("invalid input syntax for type double precision: \"%s\"",
535-
orig_num)));
563+
errmsg("invalid input syntax for type %s: \"%s\"",
564+
type_name, orig_string)));
536565

537-
CHECKFLOATVAL(val, true, true);
538-
539-
PG_RETURN_FLOAT8(val);
566+
return val;
540567
}
541568

542569
/*
@@ -547,10 +574,24 @@ Datum
547574
float8out(PG_FUNCTION_ARGS)
548575
{
549576
float8 num = PG_GETARG_FLOAT8(0);
577+
578+
PG_RETURN_CSTRING(float8out_internal(num));
579+
}
580+
581+
/*
582+
* float8out_internal - guts of float8out()
583+
*
584+
* This is exposed for use by functions that want a reasonably
585+
* platform-independent way of outputting doubles.
586+
* The result is always palloc'd.
587+
*/
588+
char *
589+
float8out_internal(double num)
590+
{
550591
char *ascii = (char *) palloc(MAXDOUBLEWIDTH + 1);
551592

552593
if (isnan(num))
553-
PG_RETURN_CSTRING(strcpy(ascii, "NaN"));
594+
return strcpy(ascii, "NaN");
554595

555596
switch (is_infinite(num))
556597
{
@@ -571,7 +612,7 @@ float8out(PG_FUNCTION_ARGS)
571612
}
572613
}
573614

574-
PG_RETURN_CSTRING(ascii);
615+
return ascii;
575616
}
576617

577618
/*

0 commit comments

Comments
 (0)