From 319902dc8c736c54e7834440786037d9bfafccef Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 28 Oct 2004 18:55:08 +0000 Subject: [PATCH] Fix to_number for the case of a trailing S. Karel Zak --- src/backend/utils/adt/formatting.c | 181 +++++++++++++++++++------- src/test/regress/expected/numeric.out | 2 +- src/test/regress/sql/numeric.sql | 2 +- 3 files changed, 139 insertions(+), 46 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index a982a0071a..26ebeaaac2 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -1,7 +1,7 @@ /* ----------------------------------------------------------------------- * formatting.c * - * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.79 2004/10/13 01:25:11 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.80 2004/10/28 18:55:06 tgl Exp $ * * * Portions Copyright (c) 1999-2004, PostgreSQL Global Development Group @@ -853,7 +853,8 @@ typedef struct NUMProc num_pre, /* space before first number */ read_dec, /* to_number - was read dec. point */ - read_post; /* to_number - number of dec. digit */ + read_post, /* to_number - number of dec. digit */ + read_pre; /* to_number - number non-dec. digit */ char *number, /* string with number */ *number_p, /* pointer to current number position */ @@ -3623,15 +3624,18 @@ get_last_relevant_decnum(char *num) static void NUM_numpart_from_char(NUMProc *Np, int id, int plen) { - + bool isread = FALSE; + #ifdef DEBUG_TO_FROM_CHAR - elog(DEBUG_elog_output, " --- scan start --- "); + elog(DEBUG_elog_output, " --- scan start --- id=%s", + (id==NUM_0 || id==NUM_9) ? "NUM_0/9" : id==NUM_DEC ? "NUM_DEC" : "???"); #endif if (*Np->inout_p == ' ') Np->inout_p++; #define OVERLOAD_TEST (Np->inout_p >= Np->inout + plen) +#define AMOUNT_TEST(_s) (plen-(Np->inout_p-Np->inout) >= _s) if (*Np->inout_p == ' ') Np->inout_p++; @@ -3640,68 +3644,73 @@ NUM_numpart_from_char(NUMProc *Np, int id, int plen) return; /* - * read sign + * read sign before number */ - if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9 || NUM_S)) + if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9 ) && + (Np->read_pre + Np->read_post)==0) { #ifdef DEBUG_TO_FROM_CHAR - elog(DEBUG_elog_output, "Try read sign (%c)", *Np->inout_p); + elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s", + *Np->inout_p, Np->L_positive_sign, Np->L_negative_sign); #endif /* * locale sign */ - if (IS_LSIGN(Np->Num)) + if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_PRE) { - - int x = strlen(Np->L_negative_sign); - + int x=0; #ifdef DEBUG_TO_FROM_CHAR - elog(DEBUG_elog_output, "Try read locale sign (%c)", *Np->inout_p); + elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *Np->inout_p); #endif - if (!strncmp(Np->inout_p, Np->L_negative_sign, x)) + if ((x = strlen(Np->L_negative_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_negative_sign, x)==0) { - Np->inout_p += x - 1; + Np->inout_p += x; *Np->number = '-'; - return; } - - x = strlen(Np->L_positive_sign); - if (!strncmp(Np->inout_p, Np->L_positive_sign, x)) + else if ((x = strlen(Np->L_positive_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_positive_sign, x)==0) { - Np->inout_p += x - 1; + Np->inout_p += x; *Np->number = '+'; - return; } } - + else + { #ifdef DEBUG_TO_FROM_CHAR - elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->inout_p); + elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->inout_p); #endif + /* + * simple + - < > + */ + if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) && + *Np->inout_p == '<')) + { - /* - * simple + - < > - */ - if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) && - *Np->inout_p == '<')) - { + *Np->number = '-'; /* set - */ + Np->inout_p++; - *Np->number = '-'; /* set - */ - Np->inout_p++; + } + else if (*Np->inout_p == '+') + { - } - else if (*Np->inout_p == '+') - { - - *Np->number = '+'; /* set + */ - Np->inout_p++; + *Np->number = '+'; /* set + */ + Np->inout_p++; + } } } if (OVERLOAD_TEST) return; - + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *Np->inout_p, Np->number); +#endif + /* * read digit */ @@ -3716,16 +3725,19 @@ NUM_numpart_from_char(NUMProc *Np, int id, int plen) if (Np->read_dec) Np->read_post++; + else + Np->read_pre++; + isread = TRUE; + #ifdef DEBUG_TO_FROM_CHAR elog(DEBUG_elog_output, "Read digit (%c)", *Np->inout_p); #endif - - /* - * read decimal point - */ + /* + * read decimal point + */ } - else if (IS_DECIMAL(Np->Num)) + else if (IS_DECIMAL(Np->Num) && Np->read_dec == FALSE) { #ifdef DEBUG_TO_FROM_CHAR @@ -3737,7 +3749,7 @@ NUM_numpart_from_char(NUMProc *Np, int id, int plen) *Np->number_p = '.'; Np->number_p++; Np->read_dec = TRUE; - + isread = TRUE; } else { @@ -3747,15 +3759,90 @@ NUM_numpart_from_char(NUMProc *Np, int id, int plen) elog(DEBUG_elog_output, "Try read locale point (%c)", *Np->inout_p); #endif - if (!strncmp(Np->inout_p, Np->decimal, x)) + if (x && AMOUNT_TEST(x) && strncmp(Np->inout_p, Np->decimal, x)==0) { Np->inout_p += x - 1; *Np->number_p = '.'; Np->number_p++; Np->read_dec = TRUE; + isread = TRUE; } } } + + if (OVERLOAD_TEST) + return; + + /* + * Read sign behind "last" number + * + * We need sign detection because determine exact position of + * post-sign is difficult: + * + * FM9999.9999999S -> 123.001- + * 9.9S -> .5- + * FM9.999999MI -> 5.01- + */ + if (*Np->number == ' ' && Np->read_pre + Np->read_post > 0) + { + /* + * locale sign (NUM_S) is always anchored behind a last number, if: + * - locale sign expected + * - last read char was NUM_0/9 or NUM_DEC + * - and next char is not digit + */ + if (IS_LSIGN(Np->Num) && isread && + (Np->inout_p+1) <= Np->inout + plen && + isdigit(*(Np->inout_p+1))==0) + { + int x; + char *tmp = Np->inout_p++; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *Np->inout_p); +#endif + if ((x = strlen(Np->L_negative_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_negative_sign, x)==0) + { + Np->inout_p += x-1; /* -1 .. NUM_processor() do inout_p++ */ + *Np->number = '-'; + } + else if ((x = strlen(Np->L_positive_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_positive_sign, x)==0) + { + Np->inout_p += x-1; /* -1 .. NUM_processor() do inout_p++ */ + *Np->number = '+'; + } + if (*Np->number == ' ') + /* no sign read */ + Np->inout_p = tmp; + } + + /* + * try read non-locale sign, it's happen only if format is not exact + * and we cannot determine sign position of MI/PL/SG, an example: + * + * FM9.999999MI -> 5.01- + * + * if (.... && IS_LSIGN(Np->Num)==FALSE) prevents read wrong formats + * like to_number('1 -', '9S') where sign is not anchored to last number. + */ + else if (isread==FALSE && IS_LSIGN(Np->Num)==FALSE && + (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))) + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->inout_p); +#endif + /* + * simple + - + */ + if (*Np->inout_p == '-' || *Np->inout_p == '+') + /* NUM_processor() do inout_p++ */ + *Np->number = *Np->inout_p; + } + } } #define IS_PREDEC_SPACE(_n) \ @@ -3978,6 +4065,7 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, Np->inout = inout; Np->last_relevant = NULL; Np->read_post = 0; + Np->read_pre = 0; Np->read_dec = FALSE; if (Np->Num->zero_start) @@ -4130,6 +4218,11 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, { /* * Create/reading digit/zero/blank/sing + * + * 'NUM_S' note: + * The locale sign is anchored to number and we read/write it + * when we work with first or last number (NUM_0/NUM_9). This + * is reason why NUM_S missing in follow switch(). */ switch (n->key->id) { diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index b95d79fb89..7d075a2a8e 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1191,7 +1191,7 @@ SELECT '' AS to_number_12, to_number('.01-', '99.99S'); | -0.01 (1 row) -SELECT '' AS to_number_13, to_number(' . 0 1 -', ' 9 9 . 9 9 S'); +SELECT '' AS to_number_13, to_number(' . 0 1-', ' 9 9 . 9 9 S'); to_number_13 | to_number --------------+----------- | -0.01 diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 06f9dfd749..ffc1673339 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -760,7 +760,7 @@ SELECT '' AS to_number_9, to_number('.0', '99999999.99999999'); SELECT '' AS to_number_10, to_number('0', '99.99'); SELECT '' AS to_number_11, to_number('.-01', 'S99.99'); SELECT '' AS to_number_12, to_number('.01-', '99.99S'); -SELECT '' AS to_number_13, to_number(' . 0 1 -', ' 9 9 . 9 9 S'); +SELECT '' AS to_number_13, to_number(' . 0 1-', ' 9 9 . 9 9 S'); -- -- Input syntax