Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * formatting.c
3 : : *
4 : : * src/backend/utils/adt/formatting.c
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1999-2026, PostgreSQL Global Development Group
8 : : *
9 : : *
10 : : * TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER();
11 : : *
12 : : * The PostgreSQL routines for a timestamp/int/float/numeric formatting,
13 : : * inspired by the Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() routines.
14 : : *
15 : : *
16 : : * Cache & Memory:
17 : : * Routines use (itself) internal cache for format pictures.
18 : : *
19 : : * The cache uses a static buffer and is persistent across transactions. If
20 : : * the format-picture is bigger than the cache buffer, the parser is called
21 : : * always.
22 : : *
23 : : * NOTE for Number version:
24 : : * All in this version is implemented as keywords ( => not used
25 : : * suffixes), because a format picture is for *one* item (number)
26 : : * only. It not is as a timestamp version, where each keyword (can)
27 : : * has suffix.
28 : : *
29 : : * NOTE for Timestamp routines:
30 : : * In this module the POSIX 'struct tm' type is *not* used, but rather
31 : : * PgSQL type, which has tm_mon based on one (*non* zero) and
32 : : * year *not* based on 1900, but is used full year number.
33 : : * Module supports AD / BC / AM / PM.
34 : : *
35 : : * Supported types for to_char():
36 : : *
37 : : * Timestamp, Numeric, int4, int8, float4, float8
38 : : *
39 : : * Supported types for reverse conversion:
40 : : *
41 : : * Timestamp - to_timestamp()
42 : : * Date - to_date()
43 : : * Numeric - to_number()
44 : : *
45 : : *
46 : : * Karel Zak
47 : : *
48 : : * TODO
49 : : * - better number building (formatting) / parsing, now it isn't
50 : : * ideal code
51 : : * - use Assert()
52 : : * - add support for number spelling
53 : : * - add support for string to string formatting (we must be better
54 : : * than Oracle :-),
55 : : * to_char('Hello', 'X X X X X') -> 'H e l l o'
56 : : *
57 : : *-------------------------------------------------------------------------
58 : : */
59 : :
60 : : #ifdef DEBUG_TO_FROM_CHAR
61 : : #define DEBUG_elog_output DEBUG3
62 : : #endif
63 : :
64 : : #include "postgres.h"
65 : :
66 : : #include <ctype.h>
67 : : #include <unistd.h>
68 : : #include <math.h>
69 : : #include <float.h>
70 : : #include <limits.h>
71 : :
72 : : #include "catalog/pg_type.h"
73 : : #include "common/int.h"
74 : : #include "mb/pg_wchar.h"
75 : : #include "nodes/miscnodes.h"
76 : : #include "parser/scansup.h"
77 : : #include "utils/builtins.h"
78 : : #include "utils/date.h"
79 : : #include "utils/datetime.h"
80 : : #include "utils/formatting.h"
81 : : #include "utils/memutils.h"
82 : : #include "utils/numeric.h"
83 : : #include "utils/pg_locale.h"
84 : : #include "varatt.h"
85 : :
86 : :
87 : : /*
88 : : * Routines flags
89 : : */
90 : : #define DCH_FLAG 0x1 /* DATE-TIME flag */
91 : : #define NUM_FLAG 0x2 /* NUMBER flag */
92 : : #define STD_FLAG 0x4 /* STANDARD flag */
93 : :
94 : : /*
95 : : * KeyWord Index (ascii from position 32 (' ') to 126 (~))
96 : : */
97 : : #define KeyWord_INDEX_SIZE ('~' - ' ')
98 : : #define KeyWord_INDEX_FILTER(_c) ((_c) <= ' ' || (_c) >= '~' ? 0 : 1)
99 : :
100 : : #define MAX_L10N_DATA 80 /* max localized day or month name */
101 : :
102 : : /*
103 : : * Format parser structs
104 : : */
105 : :
106 : : enum KeySuffixType
107 : : {
108 : : SUFFTYPE_PREFIX = 1,
109 : : SUFFTYPE_POSTFIX = 2,
110 : : };
111 : :
112 : : typedef struct
113 : : {
114 : : const char *name; /* suffix string */
115 : : size_t len; /* suffix length */
116 : : int id; /* used in node->suffix */
117 : : enum KeySuffixType type; /* prefix / postfix */
118 : : } KeySuffix;
119 : :
120 : : /*
121 : : * FromCharDateMode
122 : : *
123 : : * This value is used to nominate one of several distinct (and mutually
124 : : * exclusive) date conventions that a keyword can belong to.
125 : : */
126 : : typedef enum
127 : : {
128 : : FROM_CHAR_DATE_NONE = 0, /* Value does not affect date mode. */
129 : : FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */
130 : : FROM_CHAR_DATE_ISOWEEK, /* ISO 8601 week date */
131 : : } FromCharDateMode;
132 : :
133 : : typedef struct
134 : : {
135 : : const char *name;
136 : : size_t len;
137 : : int id;
138 : : bool is_digit;
139 : : FromCharDateMode date_mode;
140 : : } KeyWord;
141 : :
142 : : enum FormatNodeType
143 : : {
144 : : NODE_TYPE_END = 1,
145 : : NODE_TYPE_ACTION = 2,
146 : : NODE_TYPE_CHAR = 3,
147 : : NODE_TYPE_SEPARATOR = 4,
148 : : NODE_TYPE_SPACE = 5,
149 : : };
150 : :
151 : : typedef struct
152 : : {
153 : : enum FormatNodeType type;
154 : : char character[MAX_MULTIBYTE_CHAR_LEN + 1]; /* if type is CHAR */
155 : : uint8 suffix; /* keyword prefix/suffix code, if any
156 : : * (DCH_SUFFIX_*) */
157 : : const KeyWord *key; /* if type is ACTION */
158 : : } FormatNode;
159 : :
160 : :
161 : : /*
162 : : * Full months
163 : : */
164 : : static const char *const months_full[] = {
165 : : "January", "February", "March", "April", "May", "June", "July",
166 : : "August", "September", "October", "November", "December", NULL
167 : : };
168 : :
169 : : static const char *const days_short[] = {
170 : : "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
171 : : };
172 : :
173 : : /*
174 : : * AD / BC
175 : : *
176 : : * There is no 0 AD. Years go from 1 BC to 1 AD, so we make it
177 : : * positive and map year == -1 to year zero, and shift all negative
178 : : * years up one. For interval years, we just return the year.
179 : : */
180 : : #define ADJUST_YEAR(year, is_interval) ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year)))
181 : :
182 : : #define A_D_STR "A.D."
183 : : #define a_d_STR "a.d."
184 : : #define AD_STR "AD"
185 : : #define ad_STR "ad"
186 : :
187 : : #define B_C_STR "B.C."
188 : : #define b_c_STR "b.c."
189 : : #define BC_STR "BC"
190 : : #define bc_STR "bc"
191 : :
192 : : /*
193 : : * AD / BC strings for seq_search.
194 : : *
195 : : * These are given in two variants, a long form with periods and a standard
196 : : * form without.
197 : : *
198 : : * The array is laid out such that matches for AD have an even index, and
199 : : * matches for BC have an odd index. So the boolean value for BC is given by
200 : : * taking the array index of the match, modulo 2.
201 : : */
202 : : static const char *const adbc_strings[] = {ad_STR, bc_STR, AD_STR, BC_STR, NULL};
203 : : static const char *const adbc_strings_long[] = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, NULL};
204 : :
205 : : /*
206 : : * AM / PM
207 : : */
208 : : #define A_M_STR "A.M."
209 : : #define a_m_STR "a.m."
210 : : #define AM_STR "AM"
211 : : #define am_STR "am"
212 : :
213 : : #define P_M_STR "P.M."
214 : : #define p_m_STR "p.m."
215 : : #define PM_STR "PM"
216 : : #define pm_STR "pm"
217 : :
218 : : /*
219 : : * AM / PM strings for seq_search.
220 : : *
221 : : * These are given in two variants, a long form with periods and a standard
222 : : * form without.
223 : : *
224 : : * The array is laid out such that matches for AM have an even index, and
225 : : * matches for PM have an odd index. So the boolean value for PM is given by
226 : : * taking the array index of the match, modulo 2.
227 : : */
228 : : static const char *const ampm_strings[] = {am_STR, pm_STR, AM_STR, PM_STR, NULL};
229 : : static const char *const ampm_strings_long[] = {a_m_STR, p_m_STR, A_M_STR, P_M_STR, NULL};
230 : :
231 : : /*
232 : : * Months in roman-numeral
233 : : * (Must be in reverse order for seq_search (in FROM_CHAR), because
234 : : * 'VIII' must have higher precedence than 'V')
235 : : */
236 : : static const char *const rm_months_upper[] =
237 : : {"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL};
238 : :
239 : : static const char *const rm_months_lower[] =
240 : : {"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL};
241 : :
242 : : /*
243 : : * Roman numerals
244 : : */
245 : : static const char *const rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL};
246 : : static const char *const rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL};
247 : : static const char *const rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL};
248 : :
249 : : /*
250 : : * MACRO: Check if the current and next characters form a valid subtraction
251 : : * combination for roman numerals.
252 : : */
253 : : #define IS_VALID_SUB_COMB(curr, next) \
254 : : (((curr) == 'I' && ((next) == 'V' || (next) == 'X')) || \
255 : : ((curr) == 'X' && ((next) == 'L' || (next) == 'C')) || \
256 : : ((curr) == 'C' && ((next) == 'D' || (next) == 'M')))
257 : :
258 : : /*
259 : : * MACRO: Roman numeral value, or 0 if character isn't a roman numeral.
260 : : */
261 : : #define ROMAN_VAL(r) \
262 : : ((r) == 'I' ? 1 : \
263 : : (r) == 'V' ? 5 : \
264 : : (r) == 'X' ? 10 : \
265 : : (r) == 'L' ? 50 : \
266 : : (r) == 'C' ? 100 : \
267 : : (r) == 'D' ? 500 : \
268 : : (r) == 'M' ? 1000 : 0)
269 : :
270 : : /*
271 : : * 'MMMDCCCLXXXVIII' (3888) is the longest valid roman numeral (15 characters).
272 : : */
273 : : #define MAX_ROMAN_LEN 15
274 : :
275 : : /*
276 : : * Ordinal postfixes
277 : : */
278 : : static const char *const numTH[] = {"ST", "ND", "RD", "TH", NULL};
279 : : static const char *const numth[] = {"st", "nd", "rd", "th", NULL};
280 : :
281 : : /*
282 : : * Flags & Options:
283 : : */
284 : : enum TH_Case
285 : : {
286 : : TH_UPPER = 1,
287 : : TH_LOWER = 2,
288 : : };
289 : :
290 : : enum NUMDesc_lsign
291 : : {
292 : : NUM_LSIGN_PRE = -1,
293 : : NUM_LSIGN_POST = 1,
294 : : NUM_LSIGN_NONE = 0,
295 : : };
296 : :
297 : : /*
298 : : * Number description struct
299 : : */
300 : : typedef struct
301 : : {
302 : : int pre; /* (count) numbers before decimal */
303 : : int post; /* (count) numbers after decimal */
304 : : enum NUMDesc_lsign lsign; /* want locales sign */
305 : : int flag; /* number parameters (NUM_F_*) */
306 : : int pre_lsign_num; /* tmp value for lsign */
307 : : int multi; /* multiplier for 'V' */
308 : : int zero_start; /* position of first zero */
309 : : int zero_end; /* position of last zero */
310 : : bool need_locale; /* needs it locale */
311 : : } NUMDesc;
312 : :
313 : : /*
314 : : * Flags for NUMBER version
315 : : */
316 : : #define NUM_F_DECIMAL (1 << 1)
317 : : #define NUM_F_LDECIMAL (1 << 2)
318 : : #define NUM_F_ZERO (1 << 3)
319 : : #define NUM_F_BLANK (1 << 4)
320 : : #define NUM_F_FILLMODE (1 << 5)
321 : : #define NUM_F_LSIGN (1 << 6)
322 : : #define NUM_F_BRACKET (1 << 7)
323 : : #define NUM_F_MINUS (1 << 8)
324 : : #define NUM_F_PLUS (1 << 9)
325 : : #define NUM_F_ROMAN (1 << 10)
326 : : #define NUM_F_MULTI (1 << 11)
327 : : #define NUM_F_PLUS_POST (1 << 12)
328 : : #define NUM_F_MINUS_POST (1 << 13)
329 : : #define NUM_F_EEEE (1 << 14)
330 : :
331 : : /*
332 : : * Tests
333 : : */
334 : : #define IS_DECIMAL(_f) ((_f)->flag & NUM_F_DECIMAL)
335 : : #define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL)
336 : : #define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO)
337 : : #define IS_BLANK(_f) ((_f)->flag & NUM_F_BLANK)
338 : : #define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE)
339 : : #define IS_BRACKET(_f) ((_f)->flag & NUM_F_BRACKET)
340 : : #define IS_MINUS(_f) ((_f)->flag & NUM_F_MINUS)
341 : : #define IS_LSIGN(_f) ((_f)->flag & NUM_F_LSIGN)
342 : : #define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS)
343 : : #define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN)
344 : : #define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI)
345 : : #define IS_EEEE(_f) ((_f)->flag & NUM_F_EEEE)
346 : :
347 : : /*
348 : : * Format picture cache
349 : : *
350 : : * We will cache datetime format pictures up to DCH_CACHE_SIZE bytes long;
351 : : * likewise number format pictures up to NUM_CACHE_SIZE bytes long.
352 : : *
353 : : * For simplicity, the cache entries are fixed-size, so they allow for the
354 : : * worst case of a FormatNode for each byte in the picture string.
355 : : *
356 : : * The CACHE_SIZE constants are computed to make sizeof(DCHCacheEntry) and
357 : : * sizeof(NUMCacheEntry) be powers of 2, or just less than that, so that
358 : : * we don't waste too much space by palloc'ing them individually. Be sure
359 : : * to adjust those macros if you add fields to those structs.
360 : : *
361 : : * The max number of entries in each cache is DCH_CACHE_ENTRIES
362 : : * resp. NUM_CACHE_ENTRIES.
363 : : */
364 : : #define DCH_CACHE_OVERHEAD \
365 : : MAXALIGN(sizeof(bool) + sizeof(int))
366 : : #define NUM_CACHE_OVERHEAD \
367 : : MAXALIGN(sizeof(bool) + sizeof(int) + sizeof(NUMDesc))
368 : :
369 : : #define DCH_CACHE_SIZE \
370 : : ((2048 - DCH_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
371 : : #define NUM_CACHE_SIZE \
372 : : ((1024 - NUM_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
373 : :
374 : : #define DCH_CACHE_ENTRIES 20
375 : : #define NUM_CACHE_ENTRIES 20
376 : :
377 : : typedef struct
378 : : {
379 : : FormatNode format[DCH_CACHE_SIZE + 1];
380 : : char str[DCH_CACHE_SIZE + 1];
381 : : bool std;
382 : : bool valid;
383 : : int age;
384 : : } DCHCacheEntry;
385 : :
386 : : typedef struct
387 : : {
388 : : FormatNode format[NUM_CACHE_SIZE + 1];
389 : : char str[NUM_CACHE_SIZE + 1];
390 : : bool valid;
391 : : int age;
392 : : NUMDesc Num;
393 : : } NUMCacheEntry;
394 : :
395 : : /* global cache for date/time format pictures */
396 : : static DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES];
397 : : static int n_DCHCache = 0; /* current number of entries */
398 : : static int DCHCounter = 0; /* aging-event counter */
399 : :
400 : : /* global cache for number format pictures */
401 : : static NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES];
402 : : static int n_NUMCache = 0; /* current number of entries */
403 : : static int NUMCounter = 0; /* aging-event counter */
404 : :
405 : : /*
406 : : * For char->date/time conversion
407 : : */
408 : : typedef struct
409 : : {
410 : : FromCharDateMode mode;
411 : : int hh;
412 : : int pm;
413 : : int mi;
414 : : int ss;
415 : : int ssss;
416 : : int d; /* stored as 1-7, Sunday = 1, 0 means missing */
417 : : int dd;
418 : : int ddd;
419 : : int mm;
420 : : int ms;
421 : : int year;
422 : : int bc;
423 : : int ww;
424 : : int w;
425 : : int cc;
426 : : int j;
427 : : int us;
428 : : int yysz; /* is it YY or YYYY ? */
429 : : bool clock_12_hour; /* 12 or 24 hour clock? */
430 : : int tzsign; /* +1, -1, or 0 if no TZH/TZM fields */
431 : : int tzh;
432 : : int tzm;
433 : : int ff; /* fractional precision */
434 : : bool has_tz; /* was there a TZ field? */
435 : : int gmtoffset; /* GMT offset of fixed-offset zone abbrev */
436 : : pg_tz *tzp; /* pg_tz for dynamic abbrev */
437 : : const char *abbrev; /* dynamic abbrev */
438 : : } TmFromChar;
439 : :
440 : : struct fmt_tz /* do_to_timestamp's timezone info output */
441 : : {
442 : : bool has_tz; /* was there any TZ/TZH/TZM field? */
443 : : int gmtoffset; /* GMT offset in seconds */
444 : : };
445 : :
446 : : /*
447 : : * Debug
448 : : */
449 : : #ifdef DEBUG_TO_FROM_CHAR
450 : : #define DEBUG_TMFC(_X) \
451 : : elog(DEBUG_elog_output, "TMFC:\nmode %d\nhh %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\nww %d\nw %d\ncc %d\nj %d\nus: %d\nyysz: %d\nclock: %d", \
452 : : (_X)->mode, (_X)->hh, (_X)->pm, (_X)->mi, (_X)->ss, (_X)->ssss, \
453 : : (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, (_X)->year, \
454 : : (_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, (_X)->us, \
455 : : (_X)->yysz, (_X)->clock_12_hour)
456 : : #define DEBUG_TM(_X) \
457 : : elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\
458 : : (_X)->tm_sec, (_X)->tm_year,\
459 : : (_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\
460 : : (_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)
461 : : #else
462 : : #define DEBUG_TMFC(_X)
463 : : #define DEBUG_TM(_X)
464 : : #endif
465 : :
466 : : /*
467 : : * Datetime to char conversion
468 : : *
469 : : * To support intervals as well as timestamps, we use a custom "tm" struct
470 : : * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
471 : : * We omit the tm_isdst and tm_zone fields, which are not used here.
472 : : */
473 : : struct fmt_tm
474 : : {
475 : : int tm_sec;
476 : : int tm_min;
477 : : int64 tm_hour;
478 : : int tm_mday;
479 : : int tm_mon;
480 : : int tm_year;
481 : : int tm_wday;
482 : : int tm_yday;
483 : : long int tm_gmtoff;
484 : : };
485 : :
486 : : typedef struct TmToChar
487 : : {
488 : : struct fmt_tm tm; /* almost the classic 'tm' struct */
489 : : fsec_t fsec; /* fractional seconds */
490 : : const char *tzn; /* timezone */
491 : : } TmToChar;
492 : :
493 : : #define tmtcTm(_X) (&(_X)->tm)
494 : : #define tmtcTzn(_X) ((_X)->tzn)
495 : : #define tmtcFsec(_X) ((_X)->fsec)
496 : :
497 : : /* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
498 : : #define COPY_tm(_DST, _SRC) \
499 : : do { \
500 : : (_DST)->tm_sec = (_SRC)->tm_sec; \
501 : : (_DST)->tm_min = (_SRC)->tm_min; \
502 : : (_DST)->tm_hour = (_SRC)->tm_hour; \
503 : : (_DST)->tm_mday = (_SRC)->tm_mday; \
504 : : (_DST)->tm_mon = (_SRC)->tm_mon; \
505 : : (_DST)->tm_year = (_SRC)->tm_year; \
506 : : (_DST)->tm_wday = (_SRC)->tm_wday; \
507 : : (_DST)->tm_yday = (_SRC)->tm_yday; \
508 : : (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
509 : : } while(0)
510 : :
511 : : /* Caution: this is used to zero both pg_tm and fmt_tm structs */
512 : : #define ZERO_tm(_X) \
513 : : do { \
514 : : memset(_X, 0, sizeof(*(_X))); \
515 : : (_X)->tm_mday = (_X)->tm_mon = 1; \
516 : : } while(0)
517 : :
518 : : #define ZERO_tmtc(_X) \
519 : : do { \
520 : : ZERO_tm( tmtcTm(_X) ); \
521 : : tmtcFsec(_X) = 0; \
522 : : tmtcTzn(_X) = NULL; \
523 : : } while(0)
524 : :
525 : : /*
526 : : * to_char(time) appears to to_char() as an interval, so this check
527 : : * is really for interval and time data types.
528 : : */
529 : : #define INVALID_FOR_INTERVAL \
530 : : do { \
531 : : if (is_interval) \
532 : : ereport(ERROR, \
533 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
534 : : errmsg("invalid format specification for an interval value"), \
535 : : errhint("Intervals are not tied to specific calendar dates."))); \
536 : : } while(0)
537 : :
538 : : /*****************************************************************************
539 : : * KeyWord definitions
540 : : *****************************************************************************/
541 : :
542 : : /*
543 : : * Suffixes (FormatNode.suffix is an OR of these codes)
544 : : */
545 : : #define DCH_SUFFIX_FM 0x01
546 : : #define DCH_SUFFIX_TH 0x02
547 : : #define DCH_SUFFIX_th 0x04
548 : : #define DCH_SUFFIX_SP 0x08
549 : : #define DCH_SUFFIX_TM 0x10
550 : :
551 : : /*
552 : : * Suffix tests
553 : : */
554 : : static inline bool
300 peter@eisentraut.org 555 :CBC 196778 : IS_SUFFIX_TH(uint8 _s)
556 : : {
557 : 196778 : return (_s & DCH_SUFFIX_TH);
558 : : }
559 : :
560 : : static inline bool
561 : 196270 : IS_SUFFIX_th(uint8 _s)
562 : : {
563 : 196270 : return (_s & DCH_SUFFIX_th);
564 : : }
565 : :
566 : : static inline bool
567 : 196778 : IS_SUFFIX_THth(uint8 _s)
568 : : {
569 [ + + + + ]: 196778 : return IS_SUFFIX_TH(_s) || IS_SUFFIX_th(_s);
570 : : }
571 : :
572 : : static inline enum TH_Case
573 : 1524 : SUFFIX_TH_TYPE(uint8 _s)
574 : : {
575 [ + + ]: 1524 : return _s & DCH_SUFFIX_TH ? TH_UPPER : TH_LOWER;
576 : : }
577 : :
578 : : /* Oracle toggles FM behavior, we don't; see docs. */
579 : : static inline bool
580 : 116512 : IS_SUFFIX_FM(uint8 _s)
581 : : {
582 : 116512 : return (_s & DCH_SUFFIX_FM);
583 : : }
584 : :
585 : : static inline bool
586 : 9304 : IS_SUFFIX_TM(uint8 _s)
587 : : {
588 : 9304 : return (_s & DCH_SUFFIX_TM);
589 : : }
590 : :
591 : : /*
592 : : * Suffixes definition for DATE-TIME TO/FROM CHAR
593 : : */
594 : : #define TM_SUFFIX_LEN 2
595 : :
596 : : static const KeySuffix DCH_suff[] = {
597 : : {"FM", 2, DCH_SUFFIX_FM, SUFFTYPE_PREFIX},
598 : : {"fm", 2, DCH_SUFFIX_FM, SUFFTYPE_PREFIX},
599 : : {"TM", TM_SUFFIX_LEN, DCH_SUFFIX_TM, SUFFTYPE_PREFIX},
600 : : {"tm", 2, DCH_SUFFIX_TM, SUFFTYPE_PREFIX},
601 : : {"TH", 2, DCH_SUFFIX_TH, SUFFTYPE_POSTFIX},
602 : : {"th", 2, DCH_SUFFIX_th, SUFFTYPE_POSTFIX},
603 : : {"SP", 2, DCH_SUFFIX_SP, SUFFTYPE_POSTFIX},
604 : : /* last */
605 : : {NULL, 0, 0, 0}
606 : : };
607 : :
608 : :
609 : : /*
610 : : * Format-pictures (KeyWord).
611 : : *
612 : : * The KeyWord field; alphabetic sorted, *BUT* strings alike is sorted
613 : : * complicated -to-> easy:
614 : : *
615 : : * (example: "DDD","DD","Day","D" )
616 : : *
617 : : * (this specific sort needs the algorithm for sequential search for strings,
618 : : * which not has exact end; -> How keyword is in "HH12blabla" ? - "HH"
619 : : * or "HH12"? You must first try "HH12", because "HH" is in string, but
620 : : * it is not good.
621 : : *
622 : : * (!)
623 : : * - Position for the keyword is similar as position in the enum DCH/NUM_poz.
624 : : * (!)
625 : : *
626 : : * For fast search is used the 'int index[]', index is ascii table from position
627 : : * 32 (' ') to 126 (~), in this index is DCH_ / NUM_ enums for each ASCII
628 : : * position or -1 if char is not used in the KeyWord. Search example for
629 : : * string "MM":
630 : : * 1) see in index to index['M' - 32],
631 : : * 2) take keywords position (enum DCH_MI) from index
632 : : * 3) run sequential search in keywords[] from this position
633 : : */
634 : :
635 : : typedef enum
636 : : {
637 : : DCH_A_D,
638 : : DCH_A_M,
639 : : DCH_AD,
640 : : DCH_AM,
641 : : DCH_B_C,
642 : : DCH_BC,
643 : : DCH_CC,
644 : : DCH_DAY,
645 : : DCH_DDD,
646 : : DCH_DD,
647 : : DCH_DY,
648 : : DCH_Day,
649 : : DCH_Dy,
650 : : DCH_D,
651 : : DCH_FF1, /* FFn codes must be consecutive */
652 : : DCH_FF2,
653 : : DCH_FF3,
654 : : DCH_FF4,
655 : : DCH_FF5,
656 : : DCH_FF6,
657 : : DCH_FX, /* global suffix */
658 : : DCH_HH24,
659 : : DCH_HH12,
660 : : DCH_HH,
661 : : DCH_IDDD,
662 : : DCH_ID,
663 : : DCH_IW,
664 : : DCH_IYYY,
665 : : DCH_IYY,
666 : : DCH_IY,
667 : : DCH_I,
668 : : DCH_J,
669 : : DCH_MI,
670 : : DCH_MM,
671 : : DCH_MONTH,
672 : : DCH_MON,
673 : : DCH_MS,
674 : : DCH_Month,
675 : : DCH_Mon,
676 : : DCH_OF,
677 : : DCH_P_M,
678 : : DCH_PM,
679 : : DCH_Q,
680 : : DCH_RM,
681 : : DCH_SSSSS,
682 : : DCH_SSSS,
683 : : DCH_SS,
684 : : DCH_TZH,
685 : : DCH_TZM,
686 : : DCH_TZ,
687 : : DCH_US,
688 : : DCH_WW,
689 : : DCH_W,
690 : : DCH_Y_YYY,
691 : : DCH_YYYY,
692 : : DCH_YYY,
693 : : DCH_YY,
694 : : DCH_Y,
695 : : DCH_a_d,
696 : : DCH_a_m,
697 : : DCH_ad,
698 : : DCH_am,
699 : : DCH_b_c,
700 : : DCH_bc,
701 : : DCH_cc,
702 : : DCH_day,
703 : : DCH_ddd,
704 : : DCH_dd,
705 : : DCH_dy,
706 : : DCH_d,
707 : : DCH_ff1,
708 : : DCH_ff2,
709 : : DCH_ff3,
710 : : DCH_ff4,
711 : : DCH_ff5,
712 : : DCH_ff6,
713 : : DCH_fx,
714 : : DCH_hh24,
715 : : DCH_hh12,
716 : : DCH_hh,
717 : : DCH_iddd,
718 : : DCH_id,
719 : : DCH_iw,
720 : : DCH_iyyy,
721 : : DCH_iyy,
722 : : DCH_iy,
723 : : DCH_i,
724 : : DCH_j,
725 : : DCH_mi,
726 : : DCH_mm,
727 : : DCH_month,
728 : : DCH_mon,
729 : : DCH_ms,
730 : : DCH_of,
731 : : DCH_p_m,
732 : : DCH_pm,
733 : : DCH_q,
734 : : DCH_rm,
735 : : DCH_sssss,
736 : : DCH_ssss,
737 : : DCH_ss,
738 : : DCH_tzh,
739 : : DCH_tzm,
740 : : DCH_tz,
741 : : DCH_us,
742 : : DCH_ww,
743 : : DCH_w,
744 : : DCH_y_yyy,
745 : : DCH_yyyy,
746 : : DCH_yyy,
747 : : DCH_yy,
748 : : DCH_y,
749 : :
750 : : /* last */
751 : : _DCH_last_
752 : : } DCH_poz;
753 : :
754 : : typedef enum
755 : : {
756 : : NUM_COMMA,
757 : : NUM_DEC,
758 : : NUM_0,
759 : : NUM_9,
760 : : NUM_B,
761 : : NUM_C,
762 : : NUM_D,
763 : : NUM_E,
764 : : NUM_FM,
765 : : NUM_G,
766 : : NUM_L,
767 : : NUM_MI,
768 : : NUM_PL,
769 : : NUM_PR,
770 : : NUM_RN,
771 : : NUM_SG,
772 : : NUM_SP,
773 : : NUM_S,
774 : : NUM_TH,
775 : : NUM_V,
776 : : NUM_b,
777 : : NUM_c,
778 : : NUM_d,
779 : : NUM_e,
780 : : NUM_fm,
781 : : NUM_g,
782 : : NUM_l,
783 : : NUM_mi,
784 : : NUM_pl,
785 : : NUM_pr,
786 : : NUM_rn,
787 : : NUM_sg,
788 : : NUM_sp,
789 : : NUM_s,
790 : : NUM_th,
791 : : NUM_v,
792 : :
793 : : /* last */
794 : : _NUM_last_
795 : : } NUM_poz;
796 : :
797 : : /*
798 : : * KeyWords for DATE-TIME version
799 : : */
800 : : static const KeyWord DCH_keywords[] = {
801 : : /* name, len, id, is_digit, date_mode */
802 : : {"A.D.", 4, DCH_A_D, false, FROM_CHAR_DATE_NONE}, /* A */
803 : : {"A.M.", 4, DCH_A_M, false, FROM_CHAR_DATE_NONE},
804 : : {"AD", 2, DCH_AD, false, FROM_CHAR_DATE_NONE},
805 : : {"AM", 2, DCH_AM, false, FROM_CHAR_DATE_NONE},
806 : : {"B.C.", 4, DCH_B_C, false, FROM_CHAR_DATE_NONE}, /* B */
807 : : {"BC", 2, DCH_BC, false, FROM_CHAR_DATE_NONE},
808 : : {"CC", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* C */
809 : : {"DAY", 3, DCH_DAY, false, FROM_CHAR_DATE_NONE}, /* D */
810 : : {"DDD", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
811 : : {"DD", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
812 : : {"DY", 2, DCH_DY, false, FROM_CHAR_DATE_NONE},
813 : : {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
814 : : {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
815 : : {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
816 : : {"FF1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* F */
817 : : {"FF2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
818 : : {"FF3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
819 : : {"FF4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
820 : : {"FF5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
821 : : {"FF6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
822 : : {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
823 : : {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
824 : : {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
825 : : {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
826 : : {"IDDD", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* I */
827 : : {"ID", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
828 : : {"IW", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
829 : : {"IYYY", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
830 : : {"IYY", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
831 : : {"IY", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
832 : : {"I", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
833 : : {"J", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* J */
834 : : {"MI", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* M */
835 : : {"MM", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
836 : : {"MONTH", 5, DCH_MONTH, false, FROM_CHAR_DATE_GREGORIAN},
837 : : {"MON", 3, DCH_MON, false, FROM_CHAR_DATE_GREGORIAN},
838 : : {"MS", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
839 : : {"Month", 5, DCH_Month, false, FROM_CHAR_DATE_GREGORIAN},
840 : : {"Mon", 3, DCH_Mon, false, FROM_CHAR_DATE_GREGORIAN},
841 : : {"OF", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* O */
842 : : {"P.M.", 4, DCH_P_M, false, FROM_CHAR_DATE_NONE}, /* P */
843 : : {"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
844 : : {"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
845 : : {"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
846 : : {"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* S */
847 : : {"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
848 : : {"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
849 : : {"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* T */
850 : : {"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
851 : : {"TZ", 2, DCH_TZ, false, FROM_CHAR_DATE_NONE},
852 : : {"US", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* U */
853 : : {"WW", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* W */
854 : : {"W", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
855 : : {"Y,YYY", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* Y */
856 : : {"YYYY", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
857 : : {"YYY", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
858 : : {"YY", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
859 : : {"Y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},
860 : : {"a.d.", 4, DCH_a_d, false, FROM_CHAR_DATE_NONE}, /* a */
861 : : {"a.m.", 4, DCH_a_m, false, FROM_CHAR_DATE_NONE},
862 : : {"ad", 2, DCH_ad, false, FROM_CHAR_DATE_NONE},
863 : : {"am", 2, DCH_am, false, FROM_CHAR_DATE_NONE},
864 : : {"b.c.", 4, DCH_b_c, false, FROM_CHAR_DATE_NONE}, /* b */
865 : : {"bc", 2, DCH_bc, false, FROM_CHAR_DATE_NONE},
866 : : {"cc", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* c */
867 : : {"day", 3, DCH_day, false, FROM_CHAR_DATE_NONE}, /* d */
868 : : {"ddd", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
869 : : {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
870 : : {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
871 : : {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
872 : : {"ff1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* f */
873 : : {"ff2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
874 : : {"ff3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
875 : : {"ff4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
876 : : {"ff5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
877 : : {"ff6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
878 : : {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
879 : : {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
880 : : {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
881 : : {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
882 : : {"iddd", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* i */
883 : : {"id", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
884 : : {"iw", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
885 : : {"iyyy", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
886 : : {"iyy", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
887 : : {"iy", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
888 : : {"i", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
889 : : {"j", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* j */
890 : : {"mi", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* m */
891 : : {"mm", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
892 : : {"month", 5, DCH_month, false, FROM_CHAR_DATE_GREGORIAN},
893 : : {"mon", 3, DCH_mon, false, FROM_CHAR_DATE_GREGORIAN},
894 : : {"ms", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
895 : : {"of", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* o */
896 : : {"p.m.", 4, DCH_p_m, false, FROM_CHAR_DATE_NONE}, /* p */
897 : : {"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
898 : : {"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
899 : : {"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
900 : : {"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* s */
901 : : {"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
902 : : {"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
903 : : {"tzh", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* t */
904 : : {"tzm", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
905 : : {"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE},
906 : : {"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* u */
907 : : {"ww", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* w */
908 : : {"w", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
909 : : {"y,yyy", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* y */
910 : : {"yyyy", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
911 : : {"yyy", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
912 : : {"yy", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
913 : : {"y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},
914 : :
915 : : /* last */
916 : : {NULL, 0, 0, 0, 0}
917 : : };
918 : :
919 : : /*
920 : : * KeyWords for NUMBER version
921 : : *
922 : : * The is_digit and date_mode fields are not relevant here.
923 : : */
924 : : static const KeyWord NUM_keywords[] = {
925 : : /* name, len, id is in Index */
926 : : {",", 1, NUM_COMMA}, /* , */
927 : : {".", 1, NUM_DEC}, /* . */
928 : : {"0", 1, NUM_0}, /* 0 */
929 : : {"9", 1, NUM_9}, /* 9 */
930 : : {"B", 1, NUM_B}, /* B */
931 : : {"C", 1, NUM_C}, /* C */
932 : : {"D", 1, NUM_D}, /* D */
933 : : {"EEEE", 4, NUM_E}, /* E */
934 : : {"FM", 2, NUM_FM}, /* F */
935 : : {"G", 1, NUM_G}, /* G */
936 : : {"L", 1, NUM_L}, /* L */
937 : : {"MI", 2, NUM_MI}, /* M */
938 : : {"PL", 2, NUM_PL}, /* P */
939 : : {"PR", 2, NUM_PR},
940 : : {"RN", 2, NUM_RN}, /* R */
941 : : {"SG", 2, NUM_SG}, /* S */
942 : : {"SP", 2, NUM_SP},
943 : : {"S", 1, NUM_S},
944 : : {"TH", 2, NUM_TH}, /* T */
945 : : {"V", 1, NUM_V}, /* V */
946 : : {"b", 1, NUM_B}, /* b */
947 : : {"c", 1, NUM_C}, /* c */
948 : : {"d", 1, NUM_D}, /* d */
949 : : {"eeee", 4, NUM_E}, /* e */
950 : : {"fm", 2, NUM_FM}, /* f */
951 : : {"g", 1, NUM_G}, /* g */
952 : : {"l", 1, NUM_L}, /* l */
953 : : {"mi", 2, NUM_MI}, /* m */
954 : : {"pl", 2, NUM_PL}, /* p */
955 : : {"pr", 2, NUM_PR},
956 : : {"rn", 2, NUM_rn}, /* r */
957 : : {"sg", 2, NUM_SG}, /* s */
958 : : {"sp", 2, NUM_SP},
959 : : {"s", 1, NUM_S},
960 : : {"th", 2, NUM_th}, /* t */
961 : : {"v", 1, NUM_V}, /* v */
962 : :
963 : : /* last */
964 : : {NULL, 0, 0}
965 : : };
966 : :
967 : :
968 : : /*
969 : : * KeyWords index for DATE-TIME version
970 : : */
971 : : static const int DCH_index[KeyWord_INDEX_SIZE] = {
972 : : /*
973 : : * 0 1 2 3 4 5 6 7 8 9
974 : : */
975 : : /*---- first 0..31 chars are skipped ----*/
976 : :
977 : : -1, -1, -1, -1, -1, -1, -1, -1,
978 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
979 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
980 : : -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
981 : : DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
982 : : DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
983 : : -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
984 : : DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
985 : : -1, DCH_of, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tzh, DCH_us, -1, DCH_ww,
986 : : -1, DCH_y_yyy, -1, -1, -1, -1
987 : :
988 : : /*---- chars over 126 are skipped ----*/
989 : : };
990 : :
991 : : /*
992 : : * KeyWords index for NUMBER version
993 : : */
994 : : static const int NUM_index[KeyWord_INDEX_SIZE] = {
995 : : /*
996 : : * 0 1 2 3 4 5 6 7 8 9
997 : : */
998 : : /*---- first 0..31 chars are skipped ----*/
999 : :
1000 : : -1, -1, -1, -1, -1, -1, -1, -1,
1001 : : -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1,
1002 : : -1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1,
1003 : : -1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E,
1004 : : NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1,
1005 : : NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1,
1006 : : -1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c,
1007 : : NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi,
1008 : : -1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1,
1009 : : -1, -1, -1, -1, -1, -1
1010 : :
1011 : : /*---- chars over 126 are skipped ----*/
1012 : : };
1013 : :
1014 : : /*
1015 : : * Number processor struct
1016 : : */
1017 : : typedef struct NUMProc
1018 : : {
1019 : : NUMDesc *Num; /* number description */
1020 : :
1021 : : int sign, /* '-' or '+' */
1022 : : sign_wrote, /* was sign write */
1023 : : num_count, /* number of write digits */
1024 : : num_in, /* is inside number */
1025 : : num_curr, /* current position in number */
1026 : : out_pre_spaces, /* to_char: spaces needed before first digit */
1027 : :
1028 : : read_dec, /* to_number - was read dec. point */
1029 : : read_post, /* to_number - number of dec. digit */
1030 : : read_pre; /* to_number - number non-dec. digit */
1031 : :
1032 : : /*
1033 : : * Both TO_NUMBER and TO_CHAR cases read the "input" string and write to
1034 : : * the "output" buffer, but their semantics are a bit different. Notably,
1035 : : * in TO_NUMBER the input string is not null-terminated, so we need
1036 : : * input_end to identify where to stop.
1037 : : */
1038 : : const char *input, /* data input string */
1039 : : *input_p, /* pointer to current input position */
1040 : : *input_end; /* end+1 of "input" */
1041 : :
1042 : : StringInfo output; /* data output buffer */
1043 : :
1044 : : const char *last_relevant, /* last relevant number after decimal point */
1045 : :
1046 : : *L_negative_sign, /* Locale */
1047 : : *L_positive_sign,
1048 : : *decimal,
1049 : : *L_thousands_sep,
1050 : : *L_currency_symbol;
1051 : : } NUMProc;
1052 : :
1053 : : /* Return flags for DCH_from_char() */
1054 : : #define DCH_DATED 0x01
1055 : : #define DCH_TIMED 0x02
1056 : : #define DCH_ZONED 0x04
1057 : :
1058 : : /*
1059 : : * These macros are used in NUM_processor_from_char() and its subsidiary routines.
1060 : : * OVERLOAD_TEST: true if we've reached end of input string
1061 : : * AMOUNT_TEST(s): true if at least s bytes remain in string
1062 : : */
1063 : : #define OVERLOAD_TEST (Np->input_p >= Np->input_end)
1064 : : #define AMOUNT_TEST(s) (Np->input_p <= Np->input_end - (s))
1065 : :
1066 : :
1067 : : /*
1068 : : * Functions
1069 : : */
1070 : : static const KeyWord *index_seq_search(const char *str, const KeyWord *kw,
1071 : : const int *index);
1072 : : static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, enum KeySuffixType type);
1073 : : static bool is_separator_char(const char *str);
1074 : : static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
1075 : : static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
1076 : : const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
1077 : :
1078 : : static void DCH_to_char(const FormatNode *node, bool is_interval, Oid collid,
1079 : : const TmToChar *in, StringInfo out);
1080 : : static void DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
1081 : : Oid collid, bool std, Node *escontext);
1082 : :
1083 : : #ifdef DEBUG_TO_FROM_CHAR
1084 : : static void dump_index(const KeyWord *k, const int *index);
1085 : : static void dump_node(FormatNode *node, int max);
1086 : : #endif
1087 : :
1088 : : static const char *get_th(const char *num, enum TH_Case type);
1089 : : static void str_numth(StringInfo dest, int start, enum TH_Case type);
1090 : : static int adjust_partial_year_to_2020(int year);
1091 : : static size_t strspace_len(const char *str);
1092 : : static bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
1093 : : Node *escontext);
1094 : : static bool from_char_set_int(int *dest, const int value, const FormatNode *node,
1095 : : Node *escontext);
1096 : : static int from_char_parse_int_len(int *dest, const char **src, const size_t len,
1097 : : FormatNode *node, Node *escontext);
1098 : : static int from_char_parse_int(int *dest, const char **src, FormatNode *node,
1099 : : Node *escontext);
1100 : : static int seq_search_ascii(const char *name, const char *const *array, size_t *len);
1101 : : static int seq_search_localized(const char *name, char **array, size_t *len,
1102 : : Oid collid);
1103 : : static bool from_char_seq_search(int *dest, const char **src,
1104 : : const char *const *array,
1105 : : char **localized_array, Oid collid,
1106 : : FormatNode *node, Node *escontext);
1107 : : static bool do_to_timestamp(const text *date_txt, const text *fmt, Oid collid, bool std,
1108 : : struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
1109 : : int *fprec, uint32 *flags, Node *escontext);
1110 : : static void fill_str(char *str, int c, int max);
1111 : : static FormatNode *NUM_cache(int len, NUMDesc *Num, const text *pars_str, bool *shouldFree);
1112 : : static char *int_to_roman(int number);
1113 : : static int roman_to_int(NUMProc *Np);
1114 : : static void NUM_prepare_locale(NUMProc *Np);
1115 : : static const char *get_last_relevant_decnum(const char *num);
1116 : : static void NUM_numpart_from_char(NUMProc *Np, int id);
1117 : : static void NUM_numpart_to_char(NUMProc *Np, int id);
1118 : : static void NUM_processor_from_char(const FormatNode *node, NUMDesc *Num,
1119 : : const char *input, size_t input_len,
1120 : : StringInfo output,
1121 : : Oid collid);
1122 : : static void NUM_processor_to_char(const FormatNode *node, NUMDesc *Num,
1123 : : const char *input, StringInfo output,
1124 : : int out_pre_spaces, int sign, Oid collid);
1125 : : static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
1126 : : static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
1127 : : static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
1128 : : static NUMCacheEntry *NUM_cache_getnew(const char *str);
1129 : : static NUMCacheEntry *NUM_cache_search(const char *str);
1130 : : static NUMCacheEntry *NUM_cache_fetch(const char *str);
1131 : :
1132 : :
1133 : : /*
1134 : : * Fast sequential search, use index for data selection which
1135 : : * go to seq. cycle (it is very fast for unwanted strings)
1136 : : * (can't be used binary search in format parsing)
1137 : : */
1138 : : static const KeyWord *
3620 tgl@sss.pgh.pa.us 1139 : 20541 : index_seq_search(const char *str, const KeyWord *kw, const int *index)
1140 : : {
1141 : : int poz;
1142 : :
9633 bruce@momjian.us 1143 [ + + - + ]: 20541 : if (!KeyWord_INDEX_FILTER(*str))
8268 neilc@samurai.com 1144 : 4726 : return NULL;
1145 : :
302 peter@eisentraut.org 1146 [ + + ]: 15815 : if ((poz = index[*str - ' ']) > -1)
1147 : : {
7824 tgl@sss.pgh.pa.us 1148 : 14420 : const KeyWord *k = kw + poz;
1149 : :
1150 : : do
1151 : : {
5357 peter_e@gmx.net 1152 [ + + ]: 19192 : if (strncmp(str, k->name, k->len) == 0)
9711 bruce@momjian.us 1153 : 14332 : return k;
1154 : 4860 : k++;
1155 [ - + ]: 4860 : if (!k->name)
8268 neilc@samurai.com 1156 :UBC 0 : return NULL;
9633 bruce@momjian.us 1157 [ + + ]:CBC 4860 : } while (*str == *k->name);
1158 : : }
8268 neilc@samurai.com 1159 : 1483 : return NULL;
1160 : : }
1161 : :
1162 : : static const KeySuffix *
300 peter@eisentraut.org 1163 : 7771 : suff_search(const char *str, const KeySuffix *suf, enum KeySuffixType type)
1164 : : {
303 1165 [ + + ]: 60256 : for (const KeySuffix *s = suf; s->name != NULL; s++)
1166 : : {
9711 bruce@momjian.us 1167 [ + + ]: 52777 : if (s->type != type)
1168 : 24925 : continue;
1169 : :
5357 peter_e@gmx.net 1170 [ + + ]: 27852 : if (strncmp(str, s->name, s->len) == 0)
9711 bruce@momjian.us 1171 : 292 : return s;
1172 : : }
8268 neilc@samurai.com 1173 : 7479 : return NULL;
1174 : : }
1175 : :
1176 : : static bool
2909 akorotkov@postgresql 1177 : 4357 : is_separator_char(const char *str)
1178 : : {
1179 : : /* ASCII printable character, but not letter or digit */
1180 [ + - ]: 3235 : return (*str > 0x20 && *str < 0x7F &&
1181 [ + + + + ]: 3235 : !(*str >= 'A' && *str <= 'Z') &&
1182 [ + + + + : 10639 : !(*str >= 'a' && *str <= 'z') &&
- + ]
1183 [ + + + + ]: 3047 : !(*str >= '0' && *str <= '9'));
1184 : : }
1185 : :
1186 : : /*
1187 : : * Prepare NUMDesc (number description struct) via FormatNode struct
1188 : : */
1189 : : static void
4367 bruce@momjian.us 1190 : 11326 : NUMDesc_prepare(NUMDesc *num, FormatNode *n)
1191 : : {
9711 1192 [ - + ]: 11326 : if (n->type != NODE_TYPE_ACTION)
9711 bruce@momjian.us 1193 :UBC 0 : return;
1194 : :
3620 tgl@sss.pgh.pa.us 1195 [ - + - - ]:CBC 11326 : if (IS_EEEE(num) && n->key->id != NUM_E)
3620 tgl@sss.pgh.pa.us 1196 [ # # ]:UBC 0 : ereport(ERROR,
1197 : : (errcode(ERRCODE_SYNTAX_ERROR),
1198 : : errmsg("\"EEEE\" must be the last pattern used")));
1199 : :
3620 tgl@sss.pgh.pa.us 1200 [ + + - + :CBC 11326 : switch (n->key->id)
+ + + + +
+ + + + +
+ + ]
1201 : : {
1202 : 9654 : case NUM_9:
1203 [ - + ]: 9654 : if (IS_BRACKET(num))
3620 tgl@sss.pgh.pa.us 1204 [ # # ]:UBC 0 : ereport(ERROR,
1205 : : (errcode(ERRCODE_SYNTAX_ERROR),
1206 : : errmsg("\"9\" must be ahead of \"PR\"")));
3620 tgl@sss.pgh.pa.us 1207 [ + + ]:CBC 9654 : if (IS_MULTI(num))
1208 : : {
1209 : 24 : ++num->multi;
6026 bruce@momjian.us 1210 : 24 : break;
1211 : : }
3620 tgl@sss.pgh.pa.us 1212 [ + + ]: 9630 : if (IS_DECIMAL(num))
1213 : 3320 : ++num->post;
1214 : : else
1215 : 6310 : ++num->pre;
1216 : 9630 : break;
1217 : :
1218 : 345 : case NUM_0:
1219 [ - + ]: 345 : if (IS_BRACKET(num))
3620 tgl@sss.pgh.pa.us 1220 [ # # ]:UBC 0 : ereport(ERROR,
1221 : : (errcode(ERRCODE_SYNTAX_ERROR),
1222 : : errmsg("\"0\" must be ahead of \"PR\"")));
3620 tgl@sss.pgh.pa.us 1223 [ + + + + ]:CBC 345 : if (!IS_ZERO(num) && !IS_DECIMAL(num))
1224 : : {
1225 : 73 : num->flag |= NUM_F_ZERO;
1226 : 73 : num->zero_start = num->pre + 1;
1227 : : }
1228 [ + + ]: 345 : if (!IS_DECIMAL(num))
1229 : 233 : ++num->pre;
1230 : : else
1231 : 112 : ++num->post;
1232 : :
1233 : 345 : num->zero_end = num->pre + num->post;
1234 : 345 : break;
1235 : :
3620 tgl@sss.pgh.pa.us 1236 :UBC 0 : case NUM_B:
302 peter@eisentraut.org 1237 [ # # # # : 0 : if (num->pre == 0 && num->post == 0 && !IS_ZERO(num))
# # ]
3620 tgl@sss.pgh.pa.us 1238 : 0 : num->flag |= NUM_F_BLANK;
1239 : 0 : break;
1240 : :
3620 tgl@sss.pgh.pa.us 1241 :CBC 59 : case NUM_D:
1242 : 59 : num->flag |= NUM_F_LDECIMAL;
3298 peter_e@gmx.net 1243 : 59 : num->need_locale = true;
1244 : : pg_fallthrough;
3620 tgl@sss.pgh.pa.us 1245 : 323 : case NUM_DEC:
1246 [ - + ]: 323 : if (IS_DECIMAL(num))
3620 tgl@sss.pgh.pa.us 1247 [ # # ]:UBC 0 : ereport(ERROR,
1248 : : (errcode(ERRCODE_SYNTAX_ERROR),
1249 : : errmsg("multiple decimal points")));
3620 tgl@sss.pgh.pa.us 1250 [ - + ]:CBC 323 : if (IS_MULTI(num))
3620 tgl@sss.pgh.pa.us 1251 [ # # ]:UBC 0 : ereport(ERROR,
1252 : : (errcode(ERRCODE_SYNTAX_ERROR),
1253 : : errmsg("cannot use \"V\" and decimal point together")));
3620 tgl@sss.pgh.pa.us 1254 :CBC 323 : num->flag |= NUM_F_DECIMAL;
1255 : 323 : break;
1256 : :
1257 : 173 : case NUM_FM:
1258 : 173 : num->flag |= NUM_F_FILLMODE;
1259 : 173 : break;
1260 : :
1261 : 150 : case NUM_S:
1262 [ - + ]: 150 : if (IS_LSIGN(num))
3620 tgl@sss.pgh.pa.us 1263 [ # # ]:UBC 0 : ereport(ERROR,
1264 : : (errcode(ERRCODE_SYNTAX_ERROR),
1265 : : errmsg("cannot use \"S\" twice")));
3620 tgl@sss.pgh.pa.us 1266 [ + - + - :CBC 150 : if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
- + ]
3620 tgl@sss.pgh.pa.us 1267 [ # # ]:UBC 0 : ereport(ERROR,
1268 : : (errcode(ERRCODE_SYNTAX_ERROR),
1269 : : errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together")));
3620 tgl@sss.pgh.pa.us 1270 [ + + ]:CBC 150 : if (!IS_DECIMAL(num))
1271 : : {
1272 : 128 : num->lsign = NUM_LSIGN_PRE;
1273 : 128 : num->pre_lsign_num = num->pre;
3298 peter_e@gmx.net 1274 : 128 : num->need_locale = true;
3620 tgl@sss.pgh.pa.us 1275 : 128 : num->flag |= NUM_F_LSIGN;
1276 : : }
1277 [ + - ]: 22 : else if (num->lsign == NUM_LSIGN_NONE)
1278 : : {
1279 : 22 : num->lsign = NUM_LSIGN_POST;
3298 peter_e@gmx.net 1280 : 22 : num->need_locale = true;
3620 tgl@sss.pgh.pa.us 1281 : 22 : num->flag |= NUM_F_LSIGN;
1282 : : }
1283 : 150 : break;
1284 : :
1285 : 24 : case NUM_MI:
1286 [ - + ]: 24 : if (IS_LSIGN(num))
3620 tgl@sss.pgh.pa.us 1287 [ # # ]:UBC 0 : ereport(ERROR,
1288 : : (errcode(ERRCODE_SYNTAX_ERROR),
1289 : : errmsg("cannot use \"S\" and \"MI\" together")));
3620 tgl@sss.pgh.pa.us 1290 :CBC 24 : num->flag |= NUM_F_MINUS;
1291 [ + + ]: 24 : if (IS_DECIMAL(num))
1292 : 4 : num->flag |= NUM_F_MINUS_POST;
1293 : 24 : break;
1294 : :
1295 : 4 : case NUM_PL:
1296 [ - + ]: 4 : if (IS_LSIGN(num))
3620 tgl@sss.pgh.pa.us 1297 [ # # ]:UBC 0 : ereport(ERROR,
1298 : : (errcode(ERRCODE_SYNTAX_ERROR),
1299 : : errmsg("cannot use \"S\" and \"PL\" together")));
3620 tgl@sss.pgh.pa.us 1300 :CBC 4 : num->flag |= NUM_F_PLUS;
1301 [ - + ]: 4 : if (IS_DECIMAL(num))
3620 tgl@sss.pgh.pa.us 1302 :UBC 0 : num->flag |= NUM_F_PLUS_POST;
3620 tgl@sss.pgh.pa.us 1303 :CBC 4 : break;
1304 : :
1305 : 16 : case NUM_SG:
1306 [ - + ]: 16 : if (IS_LSIGN(num))
3620 tgl@sss.pgh.pa.us 1307 [ # # ]:UBC 0 : ereport(ERROR,
1308 : : (errcode(ERRCODE_SYNTAX_ERROR),
1309 : : errmsg("cannot use \"S\" and \"SG\" together")));
3620 tgl@sss.pgh.pa.us 1310 :CBC 16 : num->flag |= NUM_F_MINUS;
1311 : 16 : num->flag |= NUM_F_PLUS;
1312 : 16 : break;
1313 : :
1314 : 24 : case NUM_PR:
1315 [ + - + - : 24 : if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
- + ]
3620 tgl@sss.pgh.pa.us 1316 [ # # ]:UBC 0 : ereport(ERROR,
1317 : : (errcode(ERRCODE_SYNTAX_ERROR),
1318 : : errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
3620 tgl@sss.pgh.pa.us 1319 :CBC 24 : num->flag |= NUM_F_BRACKET;
1320 : 24 : break;
1321 : :
1322 : 44 : case NUM_rn:
1323 : : case NUM_RN:
582 1324 [ + + ]: 44 : if (IS_ROMAN(num))
1325 [ + - ]: 4 : ereport(ERROR,
1326 : : (errcode(ERRCODE_SYNTAX_ERROR),
1327 : : errmsg("cannot use \"RN\" twice")));
3620 1328 : 40 : num->flag |= NUM_F_ROMAN;
1329 : 40 : break;
1330 : :
1331 : 469 : case NUM_L:
1332 : : case NUM_G:
3298 peter_e@gmx.net 1333 : 469 : num->need_locale = true;
3620 tgl@sss.pgh.pa.us 1334 : 469 : break;
1335 : :
1336 : 12 : case NUM_V:
1337 [ - + ]: 12 : if (IS_DECIMAL(num))
3620 tgl@sss.pgh.pa.us 1338 [ # # ]:UBC 0 : ereport(ERROR,
1339 : : (errcode(ERRCODE_SYNTAX_ERROR),
1340 : : errmsg("cannot use \"V\" and decimal point together")));
3620 tgl@sss.pgh.pa.us 1341 :CBC 12 : num->flag |= NUM_F_MULTI;
1342 : 12 : break;
1343 : :
1344 : 12 : case NUM_E:
1345 [ - + ]: 12 : if (IS_EEEE(num))
3620 tgl@sss.pgh.pa.us 1346 [ # # ]:UBC 0 : ereport(ERROR,
1347 : : (errcode(ERRCODE_SYNTAX_ERROR),
1348 : : errmsg("cannot use \"EEEE\" twice")));
3620 tgl@sss.pgh.pa.us 1349 [ + - + - :CBC 12 : if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
+ - ]
1350 [ + - + - : 12 : IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
+ - ]
1351 [ + - - + ]: 12 : IS_ROMAN(num) || IS_MULTI(num))
3620 tgl@sss.pgh.pa.us 1352 [ # # ]:UBC 0 : ereport(ERROR,
1353 : : (errcode(ERRCODE_SYNTAX_ERROR),
1354 : : errmsg("\"EEEE\" is incompatible with other formats"),
1355 : : errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
3620 tgl@sss.pgh.pa.us 1356 :CBC 12 : num->flag |= NUM_F_EEEE;
1357 : 12 : break;
1358 : : }
1359 : :
582 1360 [ + + ]: 11322 : if (IS_ROMAN(num) &&
1361 [ + + ]: 40 : (num->flag & ~(NUM_F_ROMAN | NUM_F_FILLMODE)) != 0)
1362 [ + - ]: 4 : ereport(ERROR,
1363 : : (errcode(ERRCODE_SYNTAX_ERROR),
1364 : : errmsg("\"RN\" is incompatible with other formats"),
1365 : : errdetail("\"RN\" may only be used together with \"FM\".")));
1366 : : }
1367 : :
1368 : : /*
1369 : : * Format parser, search small keywords and keyword's suffixes, and make
1370 : : * format-node tree.
1371 : : *
1372 : : * for DATE-TIME & NUMBER version
1373 : : */
1374 : : static void
3620 1375 : 1232 : parse_format(FormatNode *node, const char *str, const KeyWord *kw,
1376 : : const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
1377 : : {
1378 : : FormatNode *n;
1379 : :
1380 : : #ifdef DEBUG_TO_FROM_CHAR
1381 : : elog(DEBUG_elog_output, "to_char/number(): run parser");
1382 : : #endif
1383 : :
9711 bruce@momjian.us 1384 : 1232 : n = node;
1385 : :
9633 1386 [ + + ]: 21761 : while (*str)
1387 : : {
3204 tgl@sss.pgh.pa.us 1388 : 20541 : int suffix = 0;
1389 : : const KeySuffix *s;
1390 : :
1391 : : /*
1392 : : * Prefix
1393 : : */
2528 akorotkov@postgresql 1394 [ + + + + ]: 25908 : if ((flags & DCH_FLAG) &&
3204 tgl@sss.pgh.pa.us 1395 : 5367 : (s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
1396 : : {
9711 bruce@momjian.us 1397 : 264 : suffix |= s->id;
1398 [ + - ]: 264 : if (s->len)
1399 : 264 : str += s->len;
1400 : : }
1401 : :
1402 : : /*
1403 : : * Keyword
1404 : : */
9633 1405 [ + - + + ]: 20541 : if (*str && (n->key = index_seq_search(str, kw, index)) != NULL)
1406 : : {
9711 1407 : 14332 : n->type = NODE_TYPE_ACTION;
3204 tgl@sss.pgh.pa.us 1408 : 14332 : n->suffix = suffix;
9711 bruce@momjian.us 1409 [ + - ]: 14332 : if (n->key->len)
1410 : 14332 : str += n->key->len;
1411 : :
1412 : : /*
1413 : : * NUM version: Prepare global NUMDesc struct
1414 : : */
2528 akorotkov@postgresql 1415 [ + + ]: 14332 : if (flags & NUM_FLAG)
4367 bruce@momjian.us 1416 : 11326 : NUMDesc_prepare(Num, n);
1417 : :
1418 : : /*
1419 : : * Postfix
1420 : : */
2528 akorotkov@postgresql 1421 [ + + + + : 16728 : if ((flags & DCH_FLAG) && *str &&
+ + ]
3204 tgl@sss.pgh.pa.us 1422 : 2404 : (s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
1423 : : {
1424 : 28 : n->suffix |= s->id;
9711 bruce@momjian.us 1425 [ + - ]: 28 : if (s->len)
1426 : 28 : str += s->len;
1427 : : }
1428 : :
3204 tgl@sss.pgh.pa.us 1429 : 14324 : n++;
1430 : : }
9633 bruce@momjian.us 1431 [ + - ]: 6209 : else if (*str)
1432 : : {
1433 : : int chlen;
1434 : :
2158 akorotkov@postgresql 1435 [ + + + + ]: 6209 : if ((flags & STD_FLAG) && *str != '"')
1436 : : {
1437 : : /*
1438 : : * Standard mode, allow only following separators: "-./,':; ".
1439 : : * However, we support double quotes even in standard mode
1440 : : * (see below). This is our extension of standard mode.
1441 : : */
2528 1442 [ + + ]: 380 : if (strchr("-./,':; ", *str) == NULL)
1443 [ + - ]: 4 : ereport(ERROR,
1444 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
1445 : : errmsg("invalid datetime format separator: \"%s\"",
1446 : : pnstrdup(str, pg_mblen_cstr(str)))));
1447 : :
1448 [ + + ]: 376 : if (*str == ' ')
1449 : 60 : n->type = NODE_TYPE_SPACE;
1450 : : else
1451 : 316 : n->type = NODE_TYPE_SEPARATOR;
1452 : :
1453 : 376 : n->character[0] = *str;
1454 : 376 : n->character[1] = '\0';
1455 : 376 : n->key = NULL;
1456 : 376 : n->suffix = 0;
1457 : 376 : n++;
1458 : 376 : str++;
1459 : : }
1460 [ + + ]: 5829 : else if (*str == '"')
1461 : : {
1462 : : /*
1463 : : * Process double-quoted literal string, if any
1464 : : */
3204 tgl@sss.pgh.pa.us 1465 : 256 : str++;
1466 [ + + ]: 2944 : while (*str)
1467 : : {
1468 [ + + ]: 2940 : if (*str == '"')
1469 : : {
9711 bruce@momjian.us 1470 : 252 : str++;
1471 : 252 : break;
1472 : : }
1473 : : /* backslash quotes the next character, if any */
3204 tgl@sss.pgh.pa.us 1474 [ + + + - ]: 2688 : if (*str == '\\' && *(str + 1))
1475 : 160 : str++;
232 tmunro@postgresql.or 1476 : 2688 : chlen = pg_mblen_cstr(str);
9711 bruce@momjian.us 1477 : 2688 : n->type = NODE_TYPE_CHAR;
3204 tgl@sss.pgh.pa.us 1478 : 2688 : memcpy(n->character, str, chlen);
1479 : 2688 : n->character[chlen] = '\0';
8268 neilc@samurai.com 1480 : 2688 : n->key = NULL;
9711 bruce@momjian.us 1481 : 2688 : n->suffix = 0;
3204 tgl@sss.pgh.pa.us 1482 : 2688 : n++;
1483 : 2688 : str += chlen;
1484 : : }
1485 : : }
1486 : : else
1487 : : {
1488 : : /*
1489 : : * Outside double-quoted strings, backslash is only special if
1490 : : * it immediately precedes a double quote.
1491 : : */
1492 [ + + + + ]: 5573 : if (*str == '\\' && *(str + 1) == '"')
1493 : 8 : str++;
232 tmunro@postgresql.or 1494 : 5573 : chlen = pg_mblen_cstr(str);
1495 : :
2528 akorotkov@postgresql 1496 [ + + + + ]: 5573 : if ((flags & DCH_FLAG) && is_separator_char(str))
2909 1497 : 716 : n->type = NODE_TYPE_SEPARATOR;
1498 [ + + ]: 4857 : else if (isspace((unsigned char) *str))
1499 : 4666 : n->type = NODE_TYPE_SPACE;
1500 : : else
1501 : 191 : n->type = NODE_TYPE_CHAR;
1502 : :
3204 tgl@sss.pgh.pa.us 1503 : 5573 : memcpy(n->character, str, chlen);
1504 : 5573 : n->character[chlen] = '\0';
8268 neilc@samurai.com 1505 : 5573 : n->key = NULL;
3204 tgl@sss.pgh.pa.us 1506 : 5573 : n->suffix = 0;
1507 : 5573 : n++;
1508 : 5573 : str += chlen;
1509 : : }
1510 : : }
1511 : : }
1512 : :
9711 bruce@momjian.us 1513 : 1220 : n->type = NODE_TYPE_END;
1514 : 1220 : n->suffix = 0;
1515 : 1220 : }
1516 : :
1517 : : /*
1518 : : * DEBUG: Dump the FormatNode Tree (debug)
1519 : : */
1520 : : #ifdef DEBUG_TO_FROM_CHAR
1521 : :
1522 : : #define DUMP_THth(_suf) (IS_SUFFIX_TH(_suf) ? "TH" : (IS_SUFFIX_th(_suf) ? "th" : " "))
1523 : : #define DUMP_FM(_suf) (IS_SUFFIX_FM(_suf) ? "FM" : " ")
1524 : :
1525 : : static void
1526 : : dump_node(FormatNode *node, int max)
1527 : : {
1528 : : FormatNode *n;
1529 : : int a;
1530 : :
1531 : : elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT");
1532 : :
1533 : : for (a = 0, n = node; a <= max; n++, a++)
1534 : : {
1535 : : if (n->type == NODE_TYPE_ACTION)
1536 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)",
1537 : : a, n->key->name, DUMP_THth(n->suffix), DUMP_FM(n->suffix));
1538 : : else if (n->type == NODE_TYPE_CHAR)
1539 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%s'",
1540 : : a, n->character);
1541 : : else if (n->type == NODE_TYPE_END)
1542 : : {
1543 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", a);
1544 : : return;
1545 : : }
1546 : : else
1547 : : elog(DEBUG_elog_output, "%d:\t unknown NODE!", a);
1548 : : }
1549 : : }
1550 : : #endif /* DEBUG */
1551 : :
1552 : : /*****************************************************************************
1553 : : * Private utils
1554 : : *****************************************************************************/
1555 : :
1556 : : /*
1557 : : * Return ST/ND/RD/TH for simple (1..99) numbers
1558 : : */
1559 : : static const char *
300 peter@eisentraut.org 1560 : 1556 : get_th(const char *num, enum TH_Case type)
1561 : : {
302 1562 : 1556 : size_t len = strlen(num);
1563 : : char last;
1564 : :
374 1565 [ - + ]: 1556 : Assert(len > 0);
1566 : :
302 1567 : 1556 : last = num[len - 1];
9711 bruce@momjian.us 1568 [ - + ]: 1556 : if (!isdigit((unsigned char) last))
8432 tgl@sss.pgh.pa.us 1569 [ # # ]:UBC 0 : ereport(ERROR,
1570 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1571 : : errmsg("\"%s\" is not a number", num)));
1572 : :
1573 : : /*
1574 : : * All "teens" (<x>1[0-9]) get 'TH/th', while <x>[02-9][123] still get
1575 : : * 'ST/st', 'ND/nd', 'RD/rd', respectively
1576 : : */
302 peter@eisentraut.org 1577 [ + - + + ]:CBC 1556 : if (len > 1 && num[len - 2] == '1')
9633 bruce@momjian.us 1578 : 88 : last = 0;
1579 : :
1580 [ + + + + ]: 1556 : switch (last)
1581 : : {
9711 1582 : 64 : case '1':
9633 1583 [ + + ]: 64 : if (type == TH_UPPER)
1584 : 16 : return numTH[0];
9711 1585 : 48 : return numth[0];
1586 : 32 : case '2':
9633 1587 [ - + ]: 32 : if (type == TH_UPPER)
9633 bruce@momjian.us 1588 :UBC 0 : return numTH[1];
9711 bruce@momjian.us 1589 :CBC 32 : return numth[1];
1590 : 24 : case '3':
9633 1591 [ + + ]: 24 : if (type == TH_UPPER)
1592 : 4 : return numTH[2];
1593 : 20 : return numth[2];
9711 1594 : 1436 : default:
9633 1595 [ + + ]: 1436 : if (type == TH_UPPER)
1596 : 504 : return numTH[3];
9711 1597 : 932 : return numth[3];
1598 : : }
1599 : : }
1600 : :
1601 : : /*
1602 : : * Convert string-number to ordinal string-number
1603 : : *
1604 : : * The number we are considering starts at offset "start" in dest.
1605 : : */
1606 : : static void
7 tgl@sss.pgh.pa.us 1607 :GNC 1524 : str_numth(StringInfo dest, int start, enum TH_Case type)
1608 : : {
1609 [ - + ]: 1524 : Assert(start < dest->len);
1610 : 1524 : appendStringInfoString(dest, get_th(dest->data + start, type));
9711 bruce@momjian.us 1611 :GIC 1524 : }
1612 : :
1613 : : /*****************************************************************************
1614 : : * upper/lower/initcap functions
1615 : : *****************************************************************************/
1616 : :
1617 : : /*
1618 : : * collation-aware, wide-character-aware lower function
1619 : : *
1620 : : * We pass the number of bytes so we can pass varlena and char*
1621 : : * to this function. The result is a palloc'd, null-terminated string.
1622 : : */
1623 : : char *
5679 peter_e@gmx.net 1624 :CBC 251930 : str_tolower(const char *buff, size_t nbytes, Oid collid)
1625 : : {
1626 : : char *result;
1627 : : pg_locale_t mylocale;
1628 : :
8742 bruce@momjian.us 1629 [ - + ]: 251930 : if (!buff)
8742 bruce@momjian.us 1630 :UBC 0 : return NULL;
1631 : :
1680 peter@eisentraut.org 1632 [ - + ]:CBC 251930 : if (!OidIsValid(collid))
1633 : : {
1634 : : /*
1635 : : * This typically means that the parser could not resolve a conflict
1636 : : * of implicit collations, so report it that way.
1637 : : */
1680 peter@eisentraut.org 1638 [ # # ]:UBC 0 : ereport(ERROR,
1639 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1640 : : errmsg("could not determine which collation to use for %s function",
1641 : : "lower()"),
1642 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1643 : : }
1644 : :
720 jdavis@postgresql.or 1645 :CBC 251930 : mylocale = pg_newlocale_from_collation(collid);
1646 : :
1647 : : /* C/POSIX collations use this path regardless of database encoding */
1648 [ + + ]: 251930 : if (mylocale->ctype_is_c)
1649 : : {
4923 tgl@sss.pgh.pa.us 1650 : 24289 : result = asc_tolower(buff, nbytes);
1651 : : }
1652 : : else
1653 : : {
619 jdavis@postgresql.or 1654 : 227641 : const char *src = buff;
1655 : 227641 : size_t srclen = nbytes;
1656 : : size_t dstsize;
1657 : : char *dst;
1658 : : size_t needed;
1659 : :
1660 : : /* first try buffer of equal size plus terminating NUL */
1661 : 227641 : dstsize = srclen + 1;
1662 : 227641 : dst = palloc(dstsize);
1663 : :
1664 : 227641 : needed = pg_strlower(dst, dstsize, src, srclen, mylocale);
1665 [ + + ]: 227641 : if (needed + 1 > dstsize)
1666 : : {
1667 : : /* grow buffer if needed and retry */
1668 : 66 : dstsize = needed + 1;
1669 : 66 : dst = repalloc(dst, dstsize);
1670 : 66 : needed = pg_strlower(dst, dstsize, src, srclen, mylocale);
1671 [ - + ]: 66 : Assert(needed + 1 <= dstsize);
1672 : : }
1673 : :
1674 [ - + ]: 227641 : Assert(dst[needed] == '\0');
1675 : 227641 : result = dst;
1676 : : }
1677 : :
6674 tgl@sss.pgh.pa.us 1678 : 251930 : return result;
1679 : : }
1680 : :
1681 : : /*
1682 : : * collation-aware, wide-character-aware upper function
1683 : : *
1684 : : * We pass the number of bytes so we can pass varlena and char*
1685 : : * to this function. The result is a palloc'd, null-terminated string.
1686 : : */
1687 : : char *
5679 peter_e@gmx.net 1688 : 683774 : str_toupper(const char *buff, size_t nbytes, Oid collid)
1689 : : {
1690 : : char *result;
1691 : : pg_locale_t mylocale;
1692 : :
8742 bruce@momjian.us 1693 [ - + ]: 683774 : if (!buff)
8742 bruce@momjian.us 1694 :UBC 0 : return NULL;
1695 : :
1680 peter@eisentraut.org 1696 [ - + ]:CBC 683774 : if (!OidIsValid(collid))
1697 : : {
1698 : : /*
1699 : : * This typically means that the parser could not resolve a conflict
1700 : : * of implicit collations, so report it that way.
1701 : : */
1680 peter@eisentraut.org 1702 [ # # ]:UBC 0 : ereport(ERROR,
1703 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1704 : : errmsg("could not determine which collation to use for %s function",
1705 : : "upper()"),
1706 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1707 : : }
1708 : :
720 jdavis@postgresql.or 1709 :CBC 683774 : mylocale = pg_newlocale_from_collation(collid);
1710 : :
1711 : : /* C/POSIX collations use this path regardless of database encoding */
1712 [ + + ]: 683774 : if (mylocale->ctype_is_c)
1713 : : {
4923 tgl@sss.pgh.pa.us 1714 : 2677 : result = asc_toupper(buff, nbytes);
1715 : : }
1716 : : else
1717 : : {
619 jdavis@postgresql.or 1718 : 681097 : const char *src = buff;
1719 : 681097 : size_t srclen = nbytes;
1720 : : size_t dstsize;
1721 : : char *dst;
1722 : : size_t needed;
1723 : :
1724 : : /* first try buffer of equal size plus terminating NUL */
1725 : 681097 : dstsize = srclen + 1;
1726 : 681097 : dst = palloc(dstsize);
1727 : :
1728 : 681097 : needed = pg_strupper(dst, dstsize, src, srclen, mylocale);
1729 [ + + ]: 681097 : if (needed + 1 > dstsize)
1730 : : {
1731 : : /* grow buffer if needed and retry */
1732 : 4 : dstsize = needed + 1;
1733 : 4 : dst = repalloc(dst, dstsize);
1734 : 4 : needed = pg_strupper(dst, dstsize, src, srclen, mylocale);
1735 [ - + ]: 4 : Assert(needed + 1 <= dstsize);
1736 : : }
1737 : :
1738 [ - + ]: 681097 : Assert(dst[needed] == '\0');
1739 : 681097 : result = dst;
1740 : : }
1741 : :
6674 tgl@sss.pgh.pa.us 1742 : 683774 : return result;
1743 : : }
1744 : :
1745 : : /*
1746 : : * collation-aware, wide-character-aware initcap function
1747 : : *
1748 : : * We pass the number of bytes so we can pass varlena and char*
1749 : : * to this function. The result is a palloc'd, null-terminated string.
1750 : : */
1751 : : char *
5679 peter_e@gmx.net 1752 : 167 : str_initcap(const char *buff, size_t nbytes, Oid collid)
1753 : : {
1754 : : char *result;
1755 : : pg_locale_t mylocale;
1756 : :
7140 bruce@momjian.us 1757 [ - + ]: 167 : if (!buff)
7140 bruce@momjian.us 1758 :UBC 0 : return NULL;
1759 : :
1680 peter@eisentraut.org 1760 [ - + ]:CBC 167 : if (!OidIsValid(collid))
1761 : : {
1762 : : /*
1763 : : * This typically means that the parser could not resolve a conflict
1764 : : * of implicit collations, so report it that way.
1765 : : */
1680 peter@eisentraut.org 1766 [ # # ]:UBC 0 : ereport(ERROR,
1767 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1768 : : errmsg("could not determine which collation to use for %s function",
1769 : : "initcap()"),
1770 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1771 : : }
1772 : :
720 jdavis@postgresql.or 1773 :CBC 167 : mylocale = pg_newlocale_from_collation(collid);
1774 : :
1775 : : /* C/POSIX collations use this path regardless of database encoding */
1776 [ + + ]: 167 : if (mylocale->ctype_is_c)
1777 : : {
4923 tgl@sss.pgh.pa.us 1778 : 16 : result = asc_initcap(buff, nbytes);
1779 : : }
1780 : : else
1781 : : {
619 jdavis@postgresql.or 1782 : 151 : const char *src = buff;
1783 : 151 : size_t srclen = nbytes;
1784 : : size_t dstsize;
1785 : : char *dst;
1786 : : size_t needed;
1787 : :
1788 : : /* first try buffer of equal size plus terminating NUL */
1789 : 151 : dstsize = srclen + 1;
1790 : 151 : dst = palloc(dstsize);
1791 : :
1792 : 151 : needed = pg_strtitle(dst, dstsize, src, srclen, mylocale);
1793 [ + + ]: 151 : if (needed + 1 > dstsize)
1794 : : {
1795 : : /* grow buffer if needed and retry */
1796 : 20 : dstsize = needed + 1;
1797 : 20 : dst = repalloc(dst, dstsize);
1798 : 20 : needed = pg_strtitle(dst, dstsize, src, srclen, mylocale);
1799 [ - + ]: 20 : Assert(needed + 1 <= dstsize);
1800 : : }
1801 : :
1802 [ - + ]: 151 : Assert(dst[needed] == '\0');
1803 : 151 : result = dst;
1804 : : }
1805 : :
6674 tgl@sss.pgh.pa.us 1806 : 167 : return result;
1807 : : }
1808 : :
1809 : : /*
1810 : : * collation-aware, wide-character-aware case folding
1811 : : *
1812 : : * We pass the number of bytes so we can pass varlena and char*
1813 : : * to this function. The result is a palloc'd, null-terminated string.
1814 : : */
1815 : : char *
580 jdavis@postgresql.or 1816 : 20 : str_casefold(const char *buff, size_t nbytes, Oid collid)
1817 : : {
1818 : : char *result;
1819 : : pg_locale_t mylocale;
1820 : :
1821 [ - + ]: 20 : if (!buff)
580 jdavis@postgresql.or 1822 :UBC 0 : return NULL;
1823 : :
580 jdavis@postgresql.or 1824 [ - + ]:CBC 20 : if (!OidIsValid(collid))
1825 : : {
1826 : : /*
1827 : : * This typically means that the parser could not resolve a conflict
1828 : : * of implicit collations, so report it that way.
1829 : : */
580 jdavis@postgresql.or 1830 [ # # ]:UBC 0 : ereport(ERROR,
1831 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1832 : : errmsg("could not determine which collation to use for %s function",
1833 : : "casefold()"),
1834 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1835 : : }
1836 : :
580 jdavis@postgresql.or 1837 [ - + ]:CBC 20 : if (GetDatabaseEncoding() != PG_UTF8)
580 jdavis@postgresql.or 1838 [ # # ]:UBC 0 : ereport(ERROR,
1839 : : (errcode(ERRCODE_SYNTAX_ERROR),
1840 : : errmsg("Unicode case folding can only be performed if server encoding is UTF8")));
1841 : :
580 jdavis@postgresql.or 1842 :CBC 20 : mylocale = pg_newlocale_from_collation(collid);
1843 : :
1844 : : /* C/POSIX collations use this path regardless of database encoding */
1845 [ - + ]: 20 : if (mylocale->ctype_is_c)
1846 : : {
580 jdavis@postgresql.or 1847 :UBC 0 : result = asc_tolower(buff, nbytes);
1848 : : }
1849 : : else
1850 : : {
580 jdavis@postgresql.or 1851 :CBC 20 : const char *src = buff;
1852 : 20 : size_t srclen = nbytes;
1853 : : size_t dstsize;
1854 : : char *dst;
1855 : : size_t needed;
1856 : :
1857 : : /* first try buffer of equal size plus terminating NUL */
1858 : 20 : dstsize = srclen + 1;
1859 : 20 : dst = palloc(dstsize);
1860 : :
1861 : 20 : needed = pg_strfold(dst, dstsize, src, srclen, mylocale);
1862 [ - + ]: 20 : if (needed + 1 > dstsize)
1863 : : {
1864 : : /* grow buffer if needed and retry */
580 jdavis@postgresql.or 1865 :UBC 0 : dstsize = needed + 1;
1866 : 0 : dst = repalloc(dst, dstsize);
1867 : 0 : needed = pg_strfold(dst, dstsize, src, srclen, mylocale);
1868 [ # # ]: 0 : Assert(needed + 1 <= dstsize);
1869 : : }
1870 : :
580 jdavis@postgresql.or 1871 [ - + ]:CBC 20 : Assert(dst[needed] == '\0');
1872 : 20 : result = dst;
1873 : : }
1874 : :
1875 : 20 : return result;
1876 : : }
1877 : :
1878 : : /*
1879 : : * ASCII-only lower function
1880 : : *
1881 : : * We pass the number of bytes so we can pass varlena and char*
1882 : : * to this function. The result is a palloc'd, null-terminated string.
1883 : : */
1884 : : char *
4923 tgl@sss.pgh.pa.us 1885 : 27361 : asc_tolower(const char *buff, size_t nbytes)
1886 : : {
1887 : : char *result;
1888 : :
1889 [ - + ]: 27361 : if (!buff)
4923 tgl@sss.pgh.pa.us 1890 :UBC 0 : return NULL;
1891 : :
4923 tgl@sss.pgh.pa.us 1892 :CBC 27361 : result = pnstrdup(buff, nbytes);
1893 : :
303 peter@eisentraut.org 1894 [ + + ]: 284090 : for (char *p = result; *p; p++)
4923 tgl@sss.pgh.pa.us 1895 : 256729 : *p = pg_ascii_tolower((unsigned char) *p);
1896 : :
1897 : 27361 : return result;
1898 : : }
1899 : :
1900 : : /*
1901 : : * ASCII-only upper function
1902 : : *
1903 : : * We pass the number of bytes so we can pass varlena and char*
1904 : : * to this function. The result is a palloc'd, null-terminated string.
1905 : : */
1906 : : char *
1907 : 5725 : asc_toupper(const char *buff, size_t nbytes)
1908 : : {
1909 : : char *result;
1910 : :
1911 [ - + ]: 5725 : if (!buff)
4923 tgl@sss.pgh.pa.us 1912 :UBC 0 : return NULL;
1913 : :
4923 tgl@sss.pgh.pa.us 1914 :CBC 5725 : result = pnstrdup(buff, nbytes);
1915 : :
303 peter@eisentraut.org 1916 [ + + ]: 45267 : for (char *p = result; *p; p++)
4923 tgl@sss.pgh.pa.us 1917 : 39542 : *p = pg_ascii_toupper((unsigned char) *p);
1918 : :
1919 : 5725 : return result;
1920 : : }
1921 : :
1922 : : /*
1923 : : * ASCII-only initcap function
1924 : : *
1925 : : * We pass the number of bytes so we can pass varlena and char*
1926 : : * to this function. The result is a palloc'd, null-terminated string.
1927 : : */
1928 : : char *
1929 : 16 : asc_initcap(const char *buff, size_t nbytes)
1930 : : {
1931 : : char *result;
1932 : 16 : int wasalnum = false;
1933 : :
1934 [ - + ]: 16 : if (!buff)
4923 tgl@sss.pgh.pa.us 1935 :UBC 0 : return NULL;
1936 : :
4923 tgl@sss.pgh.pa.us 1937 :CBC 16 : result = pnstrdup(buff, nbytes);
1938 : :
303 peter@eisentraut.org 1939 [ + + ]: 64 : for (char *p = result; *p; p++)
1940 : : {
1941 : : char c;
1942 : :
4923 tgl@sss.pgh.pa.us 1943 [ + + ]: 48 : if (wasalnum)
1944 : 32 : *p = c = pg_ascii_tolower((unsigned char) *p);
1945 : : else
1946 : 16 : *p = c = pg_ascii_toupper((unsigned char) *p);
1947 : : /* we don't trust isalnum() here */
1948 [ + + + - ]: 96 : wasalnum = ((c >= 'A' && c <= 'Z') ||
1949 [ + - - + : 96 : (c >= 'a' && c <= 'z') ||
- - ]
4923 tgl@sss.pgh.pa.us 1950 [ # # ]:UBC 0 : (c >= '0' && c <= '9'));
1951 : : }
1952 : :
4923 tgl@sss.pgh.pa.us 1953 :CBC 16 : return result;
1954 : : }
1955 : :
1956 : : /* convenience routines for when the input is null-terminated */
1957 : :
1958 : : static char *
5679 peter_e@gmx.net 1959 :UBC 0 : str_tolower_z(const char *buff, Oid collid)
1960 : : {
1961 : 0 : return str_tolower(buff, strlen(buff), collid);
1962 : : }
1963 : :
1964 : : static char *
1965 : 0 : str_toupper_z(const char *buff, Oid collid)
1966 : : {
1967 : 0 : return str_toupper(buff, strlen(buff), collid);
1968 : : }
1969 : :
1970 : : static char *
1971 : 0 : str_initcap_z(const char *buff, Oid collid)
1972 : : {
1973 : 0 : return str_initcap(buff, strlen(buff), collid);
1974 : : }
1975 : :
1976 : : static char *
4923 tgl@sss.pgh.pa.us 1977 :CBC 3072 : asc_tolower_z(const char *buff)
1978 : : {
1979 : 3072 : return asc_tolower(buff, strlen(buff));
1980 : : }
1981 : :
1982 : : static char *
1983 : 3048 : asc_toupper_z(const char *buff)
1984 : : {
1985 : 3048 : return asc_toupper(buff, strlen(buff));
1986 : : }
1987 : :
1988 : : /* asc_initcap_z is not currently needed */
1989 : :
1990 : :
1991 : : /*
1992 : : * Skip TM / th in FROM_CHAR
1993 : : *
1994 : : * If IS_SUFFIX_THth is on, skip two chars, assuming there are two available
1995 : : */
1996 : : #define SKIP_THth(ptr, _suf) \
1997 : : do { \
1998 : : if (IS_SUFFIX_THth(_suf)) \
1999 : : { \
2000 : : if (*(ptr)) (ptr) += pg_mblen_cstr(ptr); \
2001 : : if (*(ptr)) (ptr) += pg_mblen_cstr(ptr); \
2002 : : } \
2003 : : } while (0)
2004 : :
2005 : :
2006 : : #ifdef DEBUG_TO_FROM_CHAR
2007 : : /*
2008 : : * DEBUG: Call for debug and for index checking; (Show ASCII char
2009 : : * and defined keyword for each used position
2010 : : */
2011 : : static void
2012 : : dump_index(const KeyWord *k, const int *index)
2013 : : {
2014 : : int count = 0,
2015 : : free_i = 0;
2016 : :
2017 : : elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:");
2018 : :
2019 : : for (int i = 0; i < KeyWord_INDEX_SIZE; i++)
2020 : : {
2021 : : if (index[i] != -1)
2022 : : {
2023 : : elog(DEBUG_elog_output, "\t%c: %s, ", i + 32, k[index[i]].name);
2024 : : count++;
2025 : : }
2026 : : else
2027 : : {
2028 : : free_i++;
2029 : : elog(DEBUG_elog_output, "\t(%d) %c %d", i, i + 32, index[i]);
2030 : : }
2031 : : }
2032 : : elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d",
2033 : : count, free_i);
2034 : : }
2035 : : #endif /* DEBUG */
2036 : :
2037 : : /*
2038 : : * Return true if next format picture is not digit value
2039 : : */
2040 : : static bool
9406 bruce@momjian.us 2041 : 82567 : is_next_separator(FormatNode *n)
2042 : : {
2043 [ - + ]: 82567 : if (n->type == NODE_TYPE_END)
3298 peter_e@gmx.net 2044 :UBC 0 : return false;
2045 : :
300 peter@eisentraut.org 2046 [ + - - + ]:CBC 82567 : if (n->type == NODE_TYPE_ACTION && IS_SUFFIX_THth(n->suffix))
3298 peter_e@gmx.net 2047 :UBC 0 : return true;
2048 : :
2049 : : /*
2050 : : * Next node
2051 : : */
9289 bruce@momjian.us 2052 :CBC 82567 : n++;
2053 : :
2054 : : /* end of format string is treated like a non-digit separator */
9406 2055 [ + + ]: 82567 : if (n->type == NODE_TYPE_END)
3298 peter_e@gmx.net 2056 : 8571 : return true;
2057 : :
9406 bruce@momjian.us 2058 [ + + ]: 73996 : if (n->type == NODE_TYPE_ACTION)
2059 : : {
6732 tgl@sss.pgh.pa.us 2060 [ + + ]: 4304 : if (n->key->is_digit)
3298 peter_e@gmx.net 2061 : 320 : return false;
2062 : :
2063 : 3984 : return true;
2064 : : }
3204 tgl@sss.pgh.pa.us 2065 [ + - ]: 69692 : else if (n->character[1] == '\0' &&
2066 [ - + ]: 69692 : isdigit((unsigned char) n->character[0]))
3298 peter_e@gmx.net 2067 :UBC 0 : return false;
2068 : :
3298 peter_e@gmx.net 2069 :CBC 69692 : return true; /* some non-digit input (separator) */
2070 : : }
2071 : :
2072 : :
2073 : : static int
5468 bruce@momjian.us 2074 : 56 : adjust_partial_year_to_2020(int year)
2075 : : {
2076 : : /*
2077 : : * Adjust all dates toward 2020; this is effectively what happens when we
2078 : : * assume '70' is 1970 and '69' is 2069.
2079 : : */
2080 : : /* Force 0-69 into the 2000's */
2081 [ + + ]: 56 : if (year < 70)
2082 : 28 : return year + 2000;
2083 : : /* Force 70-99 into the 1900's */
5169 tgl@sss.pgh.pa.us 2084 [ + + ]: 28 : else if (year < 100)
5468 bruce@momjian.us 2085 : 24 : return year + 1900;
2086 : : /* Force 100-519 into the 2000's */
5169 tgl@sss.pgh.pa.us 2087 [ - + ]: 4 : else if (year < 520)
5468 bruce@momjian.us 2088 :UBC 0 : return year + 2000;
2089 : : /* Force 520-999 into the 1000's */
5169 tgl@sss.pgh.pa.us 2090 [ + - ]:CBC 4 : else if (year < 1000)
5468 bruce@momjian.us 2091 : 4 : return year + 1000;
2092 : : else
5468 bruce@momjian.us 2093 :UBC 0 : return year;
2094 : : }
2095 : :
2096 : :
2097 : : static size_t
2408 tgl@sss.pgh.pa.us 2098 :CBC 82595 : strspace_len(const char *str)
2099 : : {
302 peter@eisentraut.org 2100 : 82595 : size_t len = 0;
2101 : :
7435 bruce@momjian.us 2102 [ + - - + ]: 82595 : while (*str && isspace((unsigned char) *str))
2103 : : {
7435 bruce@momjian.us 2104 :UBC 0 : str++;
2105 : 0 : len++;
2106 : : }
7435 bruce@momjian.us 2107 :CBC 82595 : return len;
2108 : : }
2109 : :
2110 : : /*
2111 : : * Set the date mode of a from-char conversion.
2112 : : *
2113 : : * Puke if the date mode has already been set, and the caller attempts to set
2114 : : * it to a conflicting mode.
2115 : : *
2116 : : * Returns true on success, false on failure (if escontext points to an
2117 : : * ErrorSaveContext; otherwise errors are thrown).
2118 : : */
2119 : : static bool
1357 tgl@sss.pgh.pa.us 2120 : 82577 : from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
2121 : : Node *escontext)
2122 : : {
6559 2123 [ + + ]: 82577 : if (mode != FROM_CHAR_DATE_NONE)
2124 : : {
2125 [ + + ]: 36643 : if (tmfc->mode == FROM_CHAR_DATE_NONE)
2126 : 13535 : tmfc->mode = mode;
2127 [ + + ]: 23108 : else if (tmfc->mode != mode)
1357 2128 [ + - ]: 4 : ereturn(escontext, false,
2129 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2130 : : errmsg("invalid combination of date conventions"),
2131 : : errhint("Do not mix Gregorian and ISO week date conventions in a formatting template.")));
2132 : : }
2133 : 82573 : return true;
2134 : : }
2135 : :
2136 : : /*
2137 : : * Set the integer pointed to by 'dest' to the given value.
2138 : : *
2139 : : * Puke if the destination integer has previously been set to some other
2140 : : * non-zero value.
2141 : : *
2142 : : * Returns true on success, false on failure (if escontext points to an
2143 : : * ErrorSaveContext; otherwise errors are thrown).
2144 : : */
2145 : : static bool
2528 akorotkov@postgresql 2146 : 82174 : from_char_set_int(int *dest, const int value, const FormatNode *node,
2147 : : Node *escontext)
2148 : : {
6559 tgl@sss.pgh.pa.us 2149 [ + + + - ]: 82174 : if (*dest != 0 && *dest != value)
1357 2150 [ + - ]: 4 : ereturn(escontext, false,
2151 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2152 : : errmsg("conflicting values for \"%s\" field in formatting string",
2153 : : node->key->name),
2154 : : errdetail("This value contradicts a previous setting for the same field type.")));
6559 2155 : 82170 : *dest = value;
1357 2156 : 82170 : return true;
2157 : : }
2158 : :
2159 : : /*
2160 : : * Read a single integer from the source string, into the int pointed to by
2161 : : * 'dest'. If 'dest' is NULL, the result is discarded.
2162 : : *
2163 : : * In fixed-width mode (the node does not have the FM suffix), consume at most
2164 : : * 'len' characters. However, any leading whitespace isn't counted in 'len'.
2165 : : *
2166 : : * We use strtol() to recover the integer value from the source string, in
2167 : : * accordance with the given FormatNode.
2168 : : *
2169 : : * If the conversion completes successfully, src will have been advanced to
2170 : : * point at the character immediately following the last character used in the
2171 : : * conversion.
2172 : : *
2173 : : * Returns the number of characters consumed, or -1 on error (if escontext
2174 : : * points to an ErrorSaveContext; otherwise errors are thrown).
2175 : : *
2176 : : * Note that from_char_parse_int() provides a more convenient wrapper where
2177 : : * the length of the field is the same as the length of the format keyword (as
2178 : : * with DD and MI).
2179 : : */
2180 : : static int
302 peter@eisentraut.org 2181 : 82595 : from_char_parse_int_len(int *dest, const char **src, const size_t len, FormatNode *node,
2182 : : Node *escontext)
2183 : : {
2184 : : long result;
2185 : : char copy[16];
2408 tgl@sss.pgh.pa.us 2186 : 82595 : const char *init = *src;
2187 : : size_t used;
2188 : :
2189 : : /*
2190 : : * Skip any whitespace before parsing the integer.
2191 : : */
6275 2192 : 82595 : *src += strspace_len(*src);
2193 : :
2194 : : /*
2195 : : * Copy just the data to be parsed into copy[]. An Assert() is sufficient
2196 : : * protection here because "len" is a constant property of the FormatNode
2197 : : * and not dependent on the input string.
2198 : : */
7 tgl@sss.pgh.pa.us 2199 [ - + ]:GNC 82595 : Assert(len < sizeof(copy));
302 peter@eisentraut.org 2200 :CBC 82595 : used = strlcpy(copy, *src, len + 1);
2201 : :
300 2202 [ + + + + ]: 82595 : if (IS_SUFFIX_FM(node->suffix) || is_next_separator(node))
6559 tgl@sss.pgh.pa.us 2203 : 82275 : {
2204 : : /*
2205 : : * This node is in Fill Mode, or the next node is known to be a
2206 : : * non-digit value, so we just slurp as many characters as we can get.
2207 : : */
2208 : : char *endptr;
2209 : :
2210 : 82275 : errno = 0;
2408 2211 : 82275 : result = strtol(init, &endptr, 10);
2212 : 82275 : *src = endptr;
2213 : : }
2214 : : else
2215 : : {
2216 : : /*
2217 : : * We need to pull exactly the number of characters given in 'len' out
2218 : : * of the string, and convert those.
2219 : : */
2220 : : char *last;
2221 : :
6410 bruce@momjian.us 2222 [ + + ]: 320 : if (used < len)
1357 tgl@sss.pgh.pa.us 2223 [ + - ]: 4 : ereturn(escontext, -1,
2224 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2225 : : errmsg("source string too short for \"%s\" formatting field",
2226 : : node->key->name),
2227 : : errdetail("Field requires %zu characters, but only %zu remain.",
2228 : : len, used),
2229 : : errhint("If your source string is not fixed-width, try using the \"FM\" modifier.")));
2230 : :
6559 2231 : 316 : errno = 0;
6410 bruce@momjian.us 2232 : 316 : result = strtol(copy, &last, 10);
2233 : 316 : used = last - copy;
2234 : :
6559 tgl@sss.pgh.pa.us 2235 [ + - + + ]: 316 : if (used > 0 && used < len)
1357 2236 [ + - ]: 4 : ereturn(escontext, -1,
2237 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2238 : : errmsg("invalid value \"%s\" for \"%s\"",
2239 : : copy, node->key->name),
2240 : : errdetail("Field requires %zu characters, but only %zu could be parsed.",
2241 : : len, used),
2242 : : errhint("If your source string is not fixed-width, try using the \"FM\" modifier.")));
2243 : :
6559 2244 : 312 : *src += used;
2245 : : }
2246 : :
2247 [ + + ]: 82587 : if (*src == init)
1357 2248 [ + + ]: 557 : ereturn(escontext, -1,
2249 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2250 : : errmsg("invalid value \"%s\" for \"%s\"",
2251 : : copy, node->key->name),
2252 : : errdetail("Value must be an integer.")));
2253 : :
6559 2254 [ + - + - : 82030 : if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
+ + ]
1357 2255 [ + - ]: 4 : ereturn(escontext, -1,
2256 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2257 : : errmsg("value for \"%s\" in source string is out of range",
2258 : : node->key->name),
2259 : : errdetail("Value must be in the range %d to %d.",
2260 : : INT_MIN, INT_MAX)));
2261 : :
6499 heikki.linnakangas@i 2262 [ + + ]: 82026 : if (dest != NULL)
2263 : : {
1357 tgl@sss.pgh.pa.us 2264 [ - + ]: 82022 : if (!from_char_set_int(dest, (int) result, node, escontext))
1357 tgl@sss.pgh.pa.us 2265 :UBC 0 : return -1;
2266 : : }
2267 : :
6559 tgl@sss.pgh.pa.us 2268 :CBC 82026 : return *src - init;
2269 : : }
2270 : :
2271 : : /*
2272 : : * Call from_char_parse_int_len(), using the length of the format keyword as
2273 : : * the expected length of the field.
2274 : : *
2275 : : * Don't call this function if the field differs in length from the format
2276 : : * keyword (as with HH24; the keyword length is 4, but the field length is 2).
2277 : : * In such cases, call from_char_parse_int_len() instead to specify the
2278 : : * required length explicitly.
2279 : : */
2280 : : static int
1357 2281 : 58623 : from_char_parse_int(int *dest, const char **src, FormatNode *node,
2282 : : Node *escontext)
2283 : : {
2284 : 58623 : return from_char_parse_int_len(dest, src, node->key->len, node, escontext);
2285 : : }
2286 : :
2287 : : /*
2288 : : * Sequentially search null-terminated "array" for a case-insensitive match
2289 : : * to the initial character(s) of "name".
2290 : : *
2291 : : * Returns array index of match, or -1 for no match.
2292 : : *
2293 : : * *len is set to the length of the match, or 0 for no match.
2294 : : *
2295 : : * Case-insensitivity is defined per pg_ascii_tolower, so this is only
2296 : : * suitable for comparisons to ASCII strings.
2297 : : */
2298 : : static int
302 peter@eisentraut.org 2299 : 156 : seq_search_ascii(const char *name, const char *const *array, size_t *len)
2300 : : {
2301 : : unsigned char firstc;
2302 : :
6559 tgl@sss.pgh.pa.us 2303 : 156 : *len = 0;
2304 : :
2305 : : /* empty string can't match anything */
2306 [ - + ]: 156 : if (!*name)
6559 tgl@sss.pgh.pa.us 2307 :UBC 0 : return -1;
2308 : :
2309 : : /* we handle first char specially to gain some speed */
2408 tgl@sss.pgh.pa.us 2310 :CBC 156 : firstc = pg_ascii_tolower((unsigned char) *name);
2311 : :
303 peter@eisentraut.org 2312 [ + + ]: 664 : for (const char *const *a = array; *a != NULL; a++)
2313 : : {
2314 : : /* compare first chars */
2408 tgl@sss.pgh.pa.us 2315 [ + + ]: 656 : if (pg_ascii_tolower((unsigned char) **a) != firstc)
6559 2316 : 476 : continue;
2317 : :
2318 : : /* compare rest of string */
303 peter@eisentraut.org 2319 : 512 : for (const char *p = *a + 1, *n = name + 1;; p++, n++)
2320 : : {
2321 : : /* return success if we matched whole array entry */
6559 tgl@sss.pgh.pa.us 2322 [ + + ]: 512 : if (*p == '\0')
2323 : : {
2408 2324 : 148 : *len = n - name;
6559 2325 : 148 : return a - array;
2326 : : }
2327 : : /* else, must have another character in "name" ... */
2328 [ - + ]: 364 : if (*n == '\0')
6559 tgl@sss.pgh.pa.us 2329 :UBC 0 : break;
2330 : : /* ... and it must match */
2408 tgl@sss.pgh.pa.us 2331 [ + + ]:CBC 728 : if (pg_ascii_tolower((unsigned char) *p) !=
2332 : 364 : pg_ascii_tolower((unsigned char) *n))
6559 2333 : 32 : break;
2334 : : }
2335 : : }
2336 : :
2337 : 8 : return -1;
2338 : : }
2339 : :
2340 : : /*
2341 : : * Compare 'name' with 'element' in a case-insensitive way, by first
2342 : : * converting 'name' to upper case, then lower case. ('element' is already
2343 : : * case-folded that way.)
2344 : : *
2345 : : * A helper function for seq_search_localized().
2346 : : */
2347 : : static bool
22 alvherre@kurilemu.de 2348 :UBC 0 : casefold_str_cmp(const char *name, size_t name_len,
2349 : : const char *element, size_t element_len,
2350 : : pg_locale_t mylocale)
2351 : : {
2352 : : /*
2353 : : * 'name' is expected to fit in MAX_L10N_DATA, even with the case
2354 : : * conversions.
2355 : : */
2356 : : char upper_substr[MAX_L10N_DATA];
2357 : : size_t upper_substr_len;
2358 : : char lower_substr[MAX_L10N_DATA];
2359 : : size_t lower_substr_len;
2360 : :
2361 : 0 : upper_substr_len = pg_strupper(upper_substr, sizeof(upper_substr),
2362 : : name, name_len,
2363 : : mylocale);
2364 [ # # ]: 0 : if (upper_substr_len > sizeof(upper_substr) - 1)
2365 : 0 : return false; /* shouldn't happen */
2366 : 0 : lower_substr_len = pg_strlower(lower_substr, sizeof(lower_substr),
2367 : : upper_substr, upper_substr_len,
2368 : : mylocale);
2369 [ # # ]: 0 : if (lower_substr_len > sizeof(lower_substr) - 1)
2370 : 0 : return false; /* shouldn't happen */
2371 : :
2372 : 0 : return strcmp(lower_substr, element) == 0;
2373 : : }
2374 : :
2375 : : /*
2376 : : * Sequentially search an array of possibly non-English words for
2377 : : * a case-insensitive match to the initial character(s) of "name".
2378 : : *
2379 : : * This has the same API as seq_search_ascii(), but we use a more general
2380 : : * case-folding transformation to achieve case-insensitivity. Case folding
2381 : : * is done per the rules of the collation identified by "collid".
2382 : : *
2383 : : * The array is treated as const, but we don't declare it that way because
2384 : : * the arrays exported by pg_locale.c aren't const.
2385 : : */
2386 : : static int
302 peter@eisentraut.org 2387 : 0 : seq_search_localized(const char *name, char **array, size_t *len, Oid collid)
2388 : : {
22 alvherre@kurilemu.de 2389 : 0 : size_t name_len = strlen(name);
2390 : 0 : const char *name_end = name + name_len;
2391 : : char *upper_name;
2392 : : char *lower_name;
2393 : : pg_locale_t mylocale;
2394 : :
2368 tgl@sss.pgh.pa.us 2395 : 0 : *len = 0;
2396 : :
2397 : : /* empty string can't match anything */
2398 [ # # ]: 0 : if (!*name)
2399 : 0 : return -1;
2400 : :
2401 : : /*
2402 : : * The case-folding processing done below is fairly expensive, so before
2403 : : * doing that, make a quick pass to see if there is an exact match.
2404 : : */
303 peter@eisentraut.org 2405 [ # # ]: 0 : for (char **a = array; *a != NULL; a++)
2406 : : {
302 2407 : 0 : size_t element_len = strlen(*a);
2408 : :
2368 tgl@sss.pgh.pa.us 2409 [ # # ]: 0 : if (strncmp(name, *a, element_len) == 0)
2410 : : {
2411 : 0 : *len = element_len;
2412 : 0 : return a - array;
2413 : : }
2414 : : }
2415 : :
22 alvherre@kurilemu.de 2416 : 0 : mylocale = pg_newlocale_from_collation(collid);
2417 : :
2418 : : /*
2419 : : * Fold to upper case, then to lower case, so that we can match reliably
2420 : : * even in languages in which case conversions are not injective.
2421 : : */
2422 : 0 : upper_name = str_toupper(name, name_len, collid);
2368 tgl@sss.pgh.pa.us 2423 : 0 : lower_name = str_tolower(upper_name, strlen(upper_name), collid);
2424 : 0 : pfree(upper_name);
2425 : :
303 peter@eisentraut.org 2426 [ # # ]: 0 : for (char **a = array; *a != NULL; a++)
2427 : : {
2428 : : char upper_element[MAX_L10N_DATA];
2429 : : size_t upper_element_len;
2430 : : char lower_element[MAX_L10N_DATA];
2431 : : size_t lower_element_len;
2432 : :
2433 : : /* Likewise upper/lower-case array element */
22 alvherre@kurilemu.de 2434 : 0 : upper_element_len = pg_strupper(upper_element, sizeof(upper_element),
2435 : : *a, strlen(*a),
2436 : : mylocale);
2437 [ # # ]: 0 : if (upper_element_len > sizeof(upper_element) - 1)
2438 : 0 : continue; /* shouldn't happen */
2439 : 0 : lower_element_len = pg_strlower(lower_element, sizeof(lower_element),
2440 : : upper_element, upper_element_len,
2441 : : mylocale);
2442 [ # # ]: 0 : if (lower_element_len > sizeof(lower_element) - 1)
2443 : 0 : continue; /* shouldn't happen */
2444 : :
2445 : : /* Is 'lower_element' a prefix of 'lower_name' ? */
2446 [ # # ]: 0 : if (strncmp(lower_name, lower_element, lower_element_len) == 0)
2447 : : {
2448 : : /*
2449 : : * We have a match, but we still need to figure out how long the
2450 : : * match is. The case conversions could have changed the lengths
2451 : : * of either string, or both.
2452 : : */
2453 : : const char *ep;
2454 : : const char *element_end;
2455 : : size_t element_nchars;
2456 : : size_t substr_len;
2457 : : size_t substr_nchars;
2458 : :
2459 : : /*
2460 : : * First, check the easy case that the string matches as whole.
2461 : : */
2462 [ # # ]: 0 : if (strlen(lower_name) == lower_element_len)
2463 : : {
2464 : 0 : *len = name_len;
2465 : 0 : pfree(lower_name);
2466 : 0 : return a - array;
2467 : : }
2468 : :
2469 : : /*
2470 : : * Another good guess is that the case conversions did not change
2471 : : * the number of characters.
2472 : : */
2473 : :
2474 : : /* count characters in the element */
2475 : 0 : ep = lower_element;
2476 : 0 : element_end = lower_element + lower_element_len;
2477 [ # # ]: 0 : for (element_nchars = 0; ep < element_end; element_nchars++)
2478 : 0 : ep += pg_mblen_range(ep, element_end);
2479 : :
2480 : : /*
2481 : : * count the byte length of a substring of 'name' having the same
2482 : : * character count as the element
2483 : : */
2484 : 0 : substr_len = 0;
2485 : 0 : for (substr_nchars = 0;
2486 [ # # # # ]: 0 : substr_nchars < element_nchars && substr_len < name_len;
2487 : 0 : substr_nchars++)
2488 : : {
2489 : 0 : substr_len += pg_mblen_range(name + substr_len, name_end);
2490 : : }
2491 : :
2492 [ # # ]: 0 : if (casefold_str_cmp(name, substr_len, lower_element, lower_element_len, mylocale))
2493 : : {
2494 : 0 : *len = substr_len;
2495 : 0 : pfree(lower_name);
2496 : 0 : return a - array;
2497 : : }
2498 : :
2499 : : /*
2500 : : * As last resort, try the case conversion and comparison for
2501 : : * every substring from the beginning of the original string until
2502 : : * we find a match.
2503 : : */
2504 : 0 : substr_len = 0;
2505 [ # # ]: 0 : while (substr_len < name_len)
2506 : : {
2507 : 0 : substr_len += pg_mblen_range(name + substr_len, name_end);
2508 : :
2509 [ # # ]: 0 : if (casefold_str_cmp(name, substr_len,
2510 : : lower_element, lower_element_len,
2511 : : mylocale))
2512 : : {
2513 : 0 : *len = substr_len;
2514 : 0 : pfree(lower_name);
2515 : 0 : return a - array;
2516 : : }
2517 : : }
2518 : : }
2519 : : }
2520 : :
2368 tgl@sss.pgh.pa.us 2521 : 0 : pfree(lower_name);
2522 : 0 : return -1;
2523 : : }
2524 : :
2525 : : /*
2526 : : * Perform a sequential search in 'array' (or 'localized_array', if that's
2527 : : * not NULL) for an entry matching the first character(s) of the 'src'
2528 : : * string case-insensitively.
2529 : : *
2530 : : * The 'array' is presumed to be English words (all-ASCII), but
2531 : : * if 'localized_array' is supplied, that might be non-English
2532 : : * so we need a more expensive case-folding transformation
2533 : : * (which will follow the rules of the collation 'collid').
2534 : : *
2535 : : * If a match is found, copy the array index of the match into the integer
2536 : : * pointed to by 'dest' and advance 'src' to the end of the part of the string
2537 : : * which matched.
2538 : : *
2539 : : * Returns true on match, false on failure (if escontext points to an
2540 : : * ErrorSaveContext; otherwise errors are thrown).
2541 : : *
2542 : : * 'node' is used only for error reports: node->key->name identifies the
2543 : : * field type we were searching for.
2544 : : */
2545 : : static bool
2408 tgl@sss.pgh.pa.us 2546 :CBC 156 : from_char_seq_search(int *dest, const char **src, const char *const *array,
2547 : : char **localized_array, Oid collid,
2548 : : FormatNode *node, Node *escontext)
2549 : : {
2550 : : size_t len;
2551 : :
2368 2552 [ + - ]: 156 : if (localized_array == NULL)
2553 : 156 : *dest = seq_search_ascii(*src, array, &len);
2554 : : else
2368 tgl@sss.pgh.pa.us 2555 :UBC 0 : *dest = seq_search_localized(*src, localized_array, &len, collid);
2556 : :
6559 tgl@sss.pgh.pa.us 2557 [ + + ]:CBC 156 : if (len <= 0)
2558 : : {
2559 : : /*
2560 : : * In the error report, truncate the string at the next whitespace (if
2561 : : * any) to avoid including irrelevant data.
2562 : : */
2408 2563 : 8 : char *copy = pstrdup(*src);
2564 : :
303 peter@eisentraut.org 2565 [ + + ]: 40 : for (char *c = copy; *c; c++)
2566 : : {
2408 tgl@sss.pgh.pa.us 2567 [ + + ]: 36 : if (scanner_isspace(*c))
2568 : : {
2569 : 4 : *c = '\0';
2570 : 4 : break;
2571 : : }
2572 : : }
2573 : :
1357 2574 [ + - ]: 8 : ereturn(escontext, false,
2575 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2576 : : errmsg("invalid value \"%s\" for \"%s\"",
2577 : : copy, node->key->name),
2578 : : errdetail("The given value did not match any of the allowed values for this field.")));
2579 : : }
6559 2580 : 148 : *src += len;
1357 2581 : 148 : return true;
2582 : : }
2583 : :
2584 : : /*
2585 : : * Process a TmToChar struct as denoted by a list of FormatNodes.
2586 : : * The formatted data is appended to 'out'.
2587 : : */
2588 : : static void
7 tgl@sss.pgh.pa.us 2589 :GNC 6308 : DCH_to_char(const FormatNode *node, bool is_interval, Oid collid,
2590 : : const TmToChar *in, StringInfo out)
2591 : : {
2592 : 6308 : const struct fmt_tm *tm = &in->tm;
2593 : : int i;
2594 : :
2595 : : #define DCH_EMITF(...) appendStringInfo(out, __VA_ARGS__)
2596 : : #define DCH_EMITS(str) appendStringInfoString(out, str)
2597 : : #define DCH_EMITC(chr) appendStringInfoCharMacro(out, chr)
2598 : :
2599 : : /* cache localized days and months */
6674 tgl@sss.pgh.pa.us 2600 :CBC 6308 : cache_locale_time();
2601 : :
7 tgl@sss.pgh.pa.us 2602 [ + + ]:GNC 124390 : for (const FormatNode *n = node; n->type != NODE_TYPE_END; n++)
2603 : : {
2604 : : int field_start;
2605 : :
6732 tgl@sss.pgh.pa.us 2606 [ + + ]:CBC 118082 : if (n->type != NODE_TYPE_ACTION)
2607 : : {
2608 : : /* Optimize the common single-byte-string case */
7 tgl@sss.pgh.pa.us 2609 [ + - ]:GNC 69261 : if (n->character[1] == '\0')
2610 [ - + ]: 69261 : DCH_EMITC(n->character[0]);
2611 : : else
7 tgl@sss.pgh.pa.us 2612 :UNC 0 : DCH_EMITS(n->character);
6732 tgl@sss.pgh.pa.us 2613 :CBC 69261 : continue;
2614 : : }
2615 : :
2616 : : /* Remember start of this field in case we need to call str_numth */
7 tgl@sss.pgh.pa.us 2617 :GNC 48821 : field_start = out->len;
2618 : :
6732 tgl@sss.pgh.pa.us 2619 [ + - + + :CBC 48821 : switch (n->key->id)
+ + + + +
+ + + + +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - +
- ]
2620 : : {
2621 : 508 : case DCH_A_M:
2622 : : case DCH_P_M:
7 tgl@sss.pgh.pa.us 2623 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2624 : : ? P_M_STR : A_M_STR);
6732 tgl@sss.pgh.pa.us 2625 :CBC 508 : break;
6732 tgl@sss.pgh.pa.us 2626 :UBC 0 : case DCH_AM:
2627 : : case DCH_PM:
7 tgl@sss.pgh.pa.us 2628 [ # # ]:UNC 0 : DCH_EMITS((tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2629 : : ? PM_STR : AM_STR);
6732 tgl@sss.pgh.pa.us 2630 :UBC 0 : break;
6732 tgl@sss.pgh.pa.us 2631 :CBC 508 : case DCH_a_m:
2632 : : case DCH_p_m:
7 tgl@sss.pgh.pa.us 2633 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2634 : : ? p_m_STR : a_m_STR);
6732 tgl@sss.pgh.pa.us 2635 :CBC 508 : break;
2636 : 508 : case DCH_am:
2637 : : case DCH_pm:
7 tgl@sss.pgh.pa.us 2638 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2639 : : ? pm_STR : am_STR);
6732 tgl@sss.pgh.pa.us 2640 :CBC 508 : break;
2641 : 3066 : case DCH_HH:
2642 : : case DCH_HH12:
2643 : :
2644 : : /*
2645 : : * display time as shown on a 12-hour clock, even for
2646 : : * intervals
2647 : : */
7 tgl@sss.pgh.pa.us 2648 [ + + + - :GNC 3066 : DCH_EMITF("%0*lld", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+ - ]
2649 : : tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
2650 : : (long long) (HOURS_PER_DAY / 2) :
2651 : : (long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
300 peter@eisentraut.org 2652 [ - + ]:CBC 3066 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2653 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2654 :CBC 3066 : break;
2655 : 1017 : case DCH_HH24:
7 tgl@sss.pgh.pa.us 2656 [ + - + - ]:GNC 1017 : DCH_EMITF("%0*lld", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
2657 : : (long long) tm->tm_hour);
300 peter@eisentraut.org 2658 [ - + ]:CBC 1017 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2659 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2660 :CBC 1017 : break;
2661 : 3067 : case DCH_MI:
7 tgl@sss.pgh.pa.us 2662 [ + - + - ]:GNC 3067 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_min >= 0) ? 2 : 3,
2663 : : tm->tm_min);
300 peter@eisentraut.org 2664 [ - + ]:CBC 3067 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2665 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2666 :CBC 3067 : break;
2667 : 3067 : case DCH_SS:
7 tgl@sss.pgh.pa.us 2668 [ + - + - ]:GNC 3067 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_sec >= 0) ? 2 : 3,
2669 : : tm->tm_sec);
300 peter@eisentraut.org 2670 [ - + ]:CBC 3067 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2671 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2672 :CBC 3067 : break;
2673 : :
2674 : : #define DCH_to_char_fsec(frac_fmt, frac_val) \
2675 : : DCH_EMITF(frac_fmt, (int) (frac_val)); \
2676 : : if (IS_SUFFIX_THth(n->suffix)) \
2677 : : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
2678 : :
2537 akorotkov@postgresql 2679 : 64 : case DCH_FF1: /* tenth of second */
2680 [ - + ]: 64 : DCH_to_char_fsec("%01d", in->fsec / 100000);
2681 : 64 : break;
2682 : 64 : case DCH_FF2: /* hundredth of second */
2683 [ - + ]: 64 : DCH_to_char_fsec("%02d", in->fsec / 10000);
2684 : 64 : break;
2685 : 96 : case DCH_FF3:
2686 : : case DCH_MS: /* millisecond */
2687 [ - + ]: 96 : DCH_to_char_fsec("%03d", in->fsec / 1000);
6732 tgl@sss.pgh.pa.us 2688 : 96 : break;
2537 akorotkov@postgresql 2689 : 64 : case DCH_FF4: /* tenth of a millisecond */
2690 [ - + ]: 64 : DCH_to_char_fsec("%04d", in->fsec / 100);
2691 : 64 : break;
2692 : 64 : case DCH_FF5: /* hundredth of a millisecond */
2693 [ - + ]: 64 : DCH_to_char_fsec("%05d", in->fsec / 10);
2694 : 64 : break;
2695 : 96 : case DCH_FF6:
2696 : : case DCH_US: /* microsecond */
2697 [ - + ]: 96 : DCH_to_char_fsec("%06d", in->fsec);
6732 tgl@sss.pgh.pa.us 2698 : 96 : break;
2699 : : #undef DCH_to_char_fsec
2700 : 516 : case DCH_SSSS:
7 tgl@sss.pgh.pa.us 2701 :GNC 516 : DCH_EMITF("%lld",
2702 : : (long long) (tm->tm_hour * SECS_PER_HOUR +
2703 : : tm->tm_min * SECS_PER_MINUTE +
2704 : : tm->tm_sec));
300 peter@eisentraut.org 2705 [ - + ]:CBC 516 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2706 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2707 :CBC 516 : break;
2708 : 4 : case DCH_tz:
2709 [ - + - - ]: 4 : INVALID_FOR_INTERVAL;
2710 [ + - ]: 4 : if (tmtcTzn(in))
2711 : : {
2712 : : /*
2713 : : * We assume here that timezone abbreviations aren't
2714 : : * localized, so ASCII-only downcasing is sufficient.
2715 : : */
4923 2716 : 4 : char *p = asc_tolower_z(tmtcTzn(in));
2717 : :
7 tgl@sss.pgh.pa.us 2718 :GNC 4 : DCH_EMITS(p);
6732 tgl@sss.pgh.pa.us 2719 :CBC 4 : pfree(p);
2720 : : }
2721 : 4 : break;
2722 : 12 : case DCH_TZ:
2723 [ - + - - ]: 12 : INVALID_FOR_INTERVAL;
2724 [ + - ]: 12 : if (tmtcTzn(in))
7 tgl@sss.pgh.pa.us 2725 :GNC 12 : DCH_EMITS(tmtcTzn(in));
6732 tgl@sss.pgh.pa.us 2726 :CBC 12 : break;
3152 andrew@dunslane.net 2727 : 72 : case DCH_TZH:
2728 [ - + - - ]: 72 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2729 [ + + ]:GNC 72 : DCH_EMITF("%c%02d",
2730 : : (tm->tm_gmtoff >= 0) ? '+' : '-',
2731 : : abs((int) tm->tm_gmtoff) / SECS_PER_HOUR);
3152 andrew@dunslane.net 2732 :CBC 72 : break;
2733 : 72 : case DCH_TZM:
2734 [ - + - - ]: 72 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2735 :GNC 72 : DCH_EMITF("%02d",
2736 : : (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
3152 andrew@dunslane.net 2737 :CBC 72 : break;
4805 bruce@momjian.us 2738 : 72 : case DCH_OF:
2739 [ - + - - ]: 72 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2740 [ - + + + ]:GNC 72 : DCH_EMITF("%c%0*d",
2741 : : (tm->tm_gmtoff >= 0) ? '+' : '-',
2742 : : IS_SUFFIX_FM(n->suffix) ? 0 : 2,
2743 : : abs((int) tm->tm_gmtoff) / SECS_PER_HOUR);
3815 tgl@sss.pgh.pa.us 2744 [ + + ]:CBC 72 : if (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR != 0)
7 tgl@sss.pgh.pa.us 2745 :GNC 48 : DCH_EMITF(":%02d",
2746 : : (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
4805 bruce@momjian.us 2747 :CBC 72 : break;
6732 tgl@sss.pgh.pa.us 2748 : 508 : case DCH_A_D:
2749 : : case DCH_B_C:
2750 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2751 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_year <= 0 ? B_C_STR : A_D_STR));
6732 tgl@sss.pgh.pa.us 2752 :CBC 508 : break;
6732 tgl@sss.pgh.pa.us 2753 :UBC 0 : case DCH_AD:
2754 : : case DCH_BC:
2755 [ # # # # ]: 0 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2756 [ # # ]:UNC 0 : DCH_EMITS((tm->tm_year <= 0 ? BC_STR : AD_STR));
6732 tgl@sss.pgh.pa.us 2757 :UBC 0 : break;
6732 tgl@sss.pgh.pa.us 2758 :CBC 508 : case DCH_a_d:
2759 : : case DCH_b_c:
2760 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2761 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_year <= 0 ? b_c_STR : a_d_STR));
6732 tgl@sss.pgh.pa.us 2762 :CBC 508 : break;
2763 : 508 : case DCH_ad:
2764 : : case DCH_bc:
2765 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2766 [ + + ]:GNC 508 : DCH_EMITS((tm->tm_year <= 0 ? bc_STR : ad_STR));
6732 tgl@sss.pgh.pa.us 2767 :CBC 508 : break;
2768 : 1016 : case DCH_MONTH:
2769 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
2770 [ - + ]: 1016 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2771 :UBC 0 : break;
300 peter@eisentraut.org 2772 [ - + ]:CBC 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2773 :UNC 0 : DCH_EMITS(str_toupper_z(localized_full_months[tm->tm_mon - 1], collid));
2774 : : else
7 tgl@sss.pgh.pa.us 2775 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2776 : : asc_toupper_z(months_full[tm->tm_mon - 1]));
6732 tgl@sss.pgh.pa.us 2777 :CBC 1016 : break;
2778 : 1016 : case DCH_Month:
2779 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
2780 [ - + ]: 1016 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2781 :UBC 0 : break;
300 peter@eisentraut.org 2782 [ - + ]:CBC 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2783 :UNC 0 : DCH_EMITS(str_initcap_z(localized_full_months[tm->tm_mon - 1], collid));
2784 : : else
7 tgl@sss.pgh.pa.us 2785 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2786 : : months_full[tm->tm_mon - 1]);
6732 tgl@sss.pgh.pa.us 2787 :CBC 1016 : break;
2788 : 1016 : case DCH_month:
2789 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
2790 [ - + ]: 1016 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2791 :UBC 0 : break;
300 peter@eisentraut.org 2792 [ - + ]:CBC 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2793 :UNC 0 : DCH_EMITS(str_tolower_z(localized_full_months[tm->tm_mon - 1], collid));
2794 : : else
7 tgl@sss.pgh.pa.us 2795 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2796 : : asc_tolower_z(months_full[tm->tm_mon - 1]));
6732 tgl@sss.pgh.pa.us 2797 :CBC 1016 : break;
2798 : 508 : case DCH_MON:
2799 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
2800 [ - + ]: 508 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2801 :UBC 0 : break;
300 peter@eisentraut.org 2802 [ - + ]:CBC 508 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2803 :UNC 0 : DCH_EMITS(str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid));
2804 : : else
7 tgl@sss.pgh.pa.us 2805 :GNC 508 : DCH_EMITS(asc_toupper_z(months[tm->tm_mon - 1]));
6732 tgl@sss.pgh.pa.us 2806 :CBC 508 : break;
2807 : 564 : case DCH_Mon:
2808 [ - + - - ]: 564 : INVALID_FOR_INTERVAL;
2809 [ - + ]: 564 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2810 :UBC 0 : break;
300 peter@eisentraut.org 2811 [ - + ]:CBC 564 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2812 :UNC 0 : DCH_EMITS(str_initcap_z(localized_abbrev_months[tm->tm_mon - 1], collid));
2813 : : else
7 tgl@sss.pgh.pa.us 2814 :GNC 564 : DCH_EMITS(months[tm->tm_mon - 1]);
6732 tgl@sss.pgh.pa.us 2815 :CBC 564 : break;
2816 : 508 : case DCH_mon:
2817 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
2818 [ - + ]: 508 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2819 :UBC 0 : break;
300 peter@eisentraut.org 2820 [ - + ]:CBC 508 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2821 :UNC 0 : DCH_EMITS(str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid));
2822 : : else
7 tgl@sss.pgh.pa.us 2823 :GNC 508 : DCH_EMITS(asc_tolower_z(months[tm->tm_mon - 1]));
6732 tgl@sss.pgh.pa.us 2824 :CBC 508 : break;
2825 : 1284 : case DCH_MM:
7 tgl@sss.pgh.pa.us 2826 [ + + + - ]:GNC 1284 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_mon >= 0) ? 2 : 3,
2827 : : tm->tm_mon);
300 peter@eisentraut.org 2828 [ - + ]:CBC 1284 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2829 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2830 :CBC 1284 : break;
2831 : 1016 : case DCH_DAY:
2832 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2833 [ - + ]: 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2834 :UNC 0 : DCH_EMITS(str_toupper_z(localized_full_days[tm->tm_wday], collid));
2835 : : else
7 tgl@sss.pgh.pa.us 2836 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2837 : : asc_toupper_z(days[tm->tm_wday]));
6732 tgl@sss.pgh.pa.us 2838 :CBC 1016 : break;
2839 : 1016 : case DCH_Day:
2840 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2841 [ - + ]: 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2842 :UNC 0 : DCH_EMITS(str_initcap_z(localized_full_days[tm->tm_wday], collid));
2843 : : else
7 tgl@sss.pgh.pa.us 2844 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2845 : : days[tm->tm_wday]);
6732 tgl@sss.pgh.pa.us 2846 :CBC 1016 : break;
2847 : 1016 : case DCH_day:
2848 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2849 [ - + ]: 1016 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2850 :UNC 0 : DCH_EMITS(str_tolower_z(localized_full_days[tm->tm_wday], collid));
2851 : : else
7 tgl@sss.pgh.pa.us 2852 [ + + ]:GNC 1016 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
2853 : : asc_tolower_z(days[tm->tm_wday]));
6732 tgl@sss.pgh.pa.us 2854 :CBC 1016 : break;
2855 : 508 : case DCH_DY:
2856 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2857 [ - + ]: 508 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2858 :UNC 0 : DCH_EMITS(str_toupper_z(localized_abbrev_days[tm->tm_wday], collid));
2859 : : else
7 tgl@sss.pgh.pa.us 2860 :GNC 508 : DCH_EMITS(asc_toupper_z(days_short[tm->tm_wday]));
6732 tgl@sss.pgh.pa.us 2861 :CBC 508 : break;
2862 : 508 : case DCH_Dy:
2863 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2864 [ - + ]: 508 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2865 :UNC 0 : DCH_EMITS(str_initcap_z(localized_abbrev_days[tm->tm_wday], collid));
2866 : : else
7 tgl@sss.pgh.pa.us 2867 :GNC 508 : DCH_EMITS(days_short[tm->tm_wday]);
6732 tgl@sss.pgh.pa.us 2868 :CBC 508 : break;
2869 : 508 : case DCH_dy:
2870 [ - + - - ]: 508 : INVALID_FOR_INTERVAL;
300 peter@eisentraut.org 2871 [ - + ]: 508 : if (IS_SUFFIX_TM(n->suffix))
7 tgl@sss.pgh.pa.us 2872 :UNC 0 : DCH_EMITS(str_tolower_z(localized_abbrev_days[tm->tm_wday], collid));
2873 : : else
7 tgl@sss.pgh.pa.us 2874 :GNC 508 : DCH_EMITS(asc_tolower_z(days_short[tm->tm_wday]));
6732 tgl@sss.pgh.pa.us 2875 :CBC 508 : break;
2876 : 2032 : case DCH_DDD:
2877 : : case DCH_IDDD:
7 tgl@sss.pgh.pa.us 2878 [ + + + + ]:GNC 2032 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 3,
2879 : : (n->key->id == DCH_DDD) ?
2880 : : tm->tm_yday :
2881 : : date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
300 peter@eisentraut.org 2882 [ - + ]:CBC 2032 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2883 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2884 :CBC 2032 : break;
2885 : 1040 : case DCH_DD:
7 tgl@sss.pgh.pa.us 2886 [ + + ]:GNC 1040 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2, tm->tm_mday);
300 peter@eisentraut.org 2887 [ - + ]:CBC 1040 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2888 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2889 :CBC 1040 : break;
2890 : 1016 : case DCH_D:
2891 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2892 :GNC 1016 : DCH_EMITF("%d", tm->tm_wday + 1);
300 peter@eisentraut.org 2893 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2894 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2895 :CBC 1016 : break;
2896 : 1016 : case DCH_ID:
2897 [ - + - - ]: 1016 : INVALID_FOR_INTERVAL;
7 tgl@sss.pgh.pa.us 2898 [ + + ]:GNC 1016 : DCH_EMITF("%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
300 peter@eisentraut.org 2899 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2900 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2901 :CBC 1016 : break;
2902 : 1016 : case DCH_WW:
7 tgl@sss.pgh.pa.us 2903 [ + + ]:GNC 1016 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2,
2904 : : (tm->tm_yday - 1) / 7 + 1);
300 peter@eisentraut.org 2905 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2906 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2907 :CBC 1016 : break;
2908 : 1016 : case DCH_IW:
7 tgl@sss.pgh.pa.us 2909 [ + + ]:GNC 1016 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2,
2910 : : date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
300 peter@eisentraut.org 2911 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2912 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2913 :CBC 1016 : break;
2914 : 1016 : case DCH_Q:
2915 [ - + ]: 1016 : if (!tm->tm_mon)
6732 tgl@sss.pgh.pa.us 2916 :UBC 0 : break;
7 tgl@sss.pgh.pa.us 2917 :GNC 1016 : DCH_EMITF("%d", (tm->tm_mon - 1) / 3 + 1);
300 peter@eisentraut.org 2918 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2919 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2920 :CBC 1016 : break;
2921 : 1016 : case DCH_CC:
6286 bruce@momjian.us 2922 [ - + ]: 1016 : if (is_interval) /* straight calculation */
6732 tgl@sss.pgh.pa.us 2923 :UBC 0 : i = tm->tm_year / 100;
2924 : : else
2925 : : {
5133 bruce@momjian.us 2926 [ + + ]:CBC 1016 : if (tm->tm_year > 0)
2927 : : /* Century 20 == 1901 - 2000 */
2928 : 1000 : i = (tm->tm_year - 1) / 100 + 1;
2929 : : else
2930 : : /* Century 6BC == 600BC - 501BC */
2931 : 16 : i = tm->tm_year / 100 - 1;
2932 : : }
6732 tgl@sss.pgh.pa.us 2933 [ + - + - ]: 1016 : if (i <= 99 && i >= -99)
7 tgl@sss.pgh.pa.us 2934 [ + + + + ]:GNC 1016 : DCH_EMITF("%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (i >= 0) ? 2 : 3, i);
2935 : : else
7 tgl@sss.pgh.pa.us 2936 :UNC 0 : DCH_EMITF("%d", i);
300 peter@eisentraut.org 2937 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2938 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2939 :CBC 1016 : break;
2940 : 1016 : case DCH_Y_YYY:
2941 [ - + + + ]: 1016 : i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
7 tgl@sss.pgh.pa.us 2942 [ - + + + ]:GNC 1016 : DCH_EMITF("%d,%03d", i,
2943 : : ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
300 peter@eisentraut.org 2944 [ - + ]:CBC 1016 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2945 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2946 :CBC 1016 : break;
2947 : 4840 : case DCH_YYYY:
2948 : : case DCH_IYYY:
7 tgl@sss.pgh.pa.us 2949 [ + + - + :GNC 4840 : DCH_EMITF("%0*d",
+ + - + +
+ + + - +
+ + + - ]
2950 : : IS_SUFFIX_FM(n->suffix) ? 0 :
2951 : : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 4 : 5,
2952 : : (n->key->id == DCH_YYYY ?
2953 : : ADJUST_YEAR(tm->tm_year, is_interval) :
2954 : : ADJUST_YEAR(date2isoyear(tm->tm_year,
2955 : : tm->tm_mon,
2956 : : tm->tm_mday),
2957 : : is_interval)));
300 peter@eisentraut.org 2958 [ + + ]:CBC 4840 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2959 :GNC 1016 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2960 :CBC 4840 : break;
2961 : 2032 : case DCH_YYY:
2962 : : case DCH_IYY:
7 tgl@sss.pgh.pa.us 2963 [ + + - + :GNC 2032 : DCH_EMITF("%0*d",
+ + - + +
+ + + - +
+ + + - ]
2964 : : IS_SUFFIX_FM(n->suffix) ? 0 :
2965 : : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 3 : 4,
2966 : : (n->key->id == DCH_YYY ?
2967 : : ADJUST_YEAR(tm->tm_year, is_interval) :
2968 : : ADJUST_YEAR(date2isoyear(tm->tm_year,
2969 : : tm->tm_mon,
2970 : : tm->tm_mday),
2971 : : is_interval)) % 1000);
300 peter@eisentraut.org 2972 [ - + ]:CBC 2032 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2973 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2974 :CBC 2032 : break;
2975 : 2032 : case DCH_YY:
2976 : : case DCH_IY:
7 tgl@sss.pgh.pa.us 2977 [ + + - + :GNC 2032 : DCH_EMITF("%0*d",
+ + - + +
+ + + - +
+ + + - ]
2978 : : IS_SUFFIX_FM(n->suffix) ? 0 :
2979 : : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 2 : 3,
2980 : : (n->key->id == DCH_YY ?
2981 : : ADJUST_YEAR(tm->tm_year, is_interval) :
2982 : : ADJUST_YEAR(date2isoyear(tm->tm_year,
2983 : : tm->tm_mon,
2984 : : tm->tm_mday),
2985 : : is_interval)) % 100);
300 peter@eisentraut.org 2986 [ - + ]:CBC 2032 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2987 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 2988 :CBC 2032 : break;
2989 : 2032 : case DCH_Y:
2990 : : case DCH_I:
7 tgl@sss.pgh.pa.us 2991 [ + + - + :GNC 2032 : DCH_EMITF("%1d",
+ + - + +
+ ]
2992 : : (n->key->id == DCH_Y ?
2993 : : ADJUST_YEAR(tm->tm_year, is_interval) :
2994 : : ADJUST_YEAR(date2isoyear(tm->tm_year,
2995 : : tm->tm_mon,
2996 : : tm->tm_mday),
2997 : : is_interval)) % 10);
300 peter@eisentraut.org 2998 [ - + ]:CBC 2032 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 2999 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 3000 :CBC 2032 : break;
3001 : 1232 : case DCH_RM:
3002 : : pg_fallthrough;
3003 : : case DCH_rm:
3004 : :
3005 : : /*
3006 : : * For intervals, values like '12 month' will be reduced to 0
3007 : : * month and some years. These should be processed.
3008 : : */
1963 michael@paquier.xyz 3009 [ + + + + ]: 1232 : if (!tm->tm_mon && !tm->tm_year)
3010 : : break;
3011 : : else
3012 : : {
3013 : 1224 : int mon = 0;
3014 : : const char *const *months;
3015 : :
3016 [ + + ]: 1224 : if (n->key->id == DCH_RM)
3017 : 1120 : months = rm_months_upper;
3018 : : else
3019 : 104 : months = rm_months_lower;
3020 : :
3021 : : /*
3022 : : * Compute the position in the roman-numeral array. Note
3023 : : * that the contents of the array are reversed, December
3024 : : * being first and January last.
3025 : : */
3026 [ + + ]: 1224 : if (tm->tm_mon == 0)
3027 : : {
3028 : : /*
3029 : : * This case is special, and tracks the case of full
3030 : : * interval years.
3031 : : */
3032 [ + + ]: 16 : mon = tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1;
3033 : : }
3034 [ + + ]: 1208 : else if (tm->tm_mon < 0)
3035 : : {
3036 : : /*
3037 : : * Negative case. In this case, the calculation is
3038 : : * reversed, where -1 means December, -2 November,
3039 : : * etc.
3040 : : */
3041 : 96 : mon = -1 * (tm->tm_mon + 1);
3042 : : }
3043 : : else
3044 : : {
3045 : : /*
3046 : : * Common case, with a strictly positive value. The
3047 : : * position in the array matches with the value of
3048 : : * tm_mon.
3049 : : */
3050 : 1112 : mon = MONTHS_PER_YEAR - tm->tm_mon;
3051 : : }
3052 : :
7 tgl@sss.pgh.pa.us 3053 [ + + ]:GNC 1224 : DCH_EMITF("%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -4,
3054 : : months[mon]);
3055 : : }
6732 tgl@sss.pgh.pa.us 3056 :CBC 1224 : break;
6732 tgl@sss.pgh.pa.us 3057 :UBC 0 : case DCH_W:
7 tgl@sss.pgh.pa.us 3058 :UNC 0 : DCH_EMITF("%d", (tm->tm_mday - 1) / 7 + 1);
300 peter@eisentraut.org 3059 [ # # ]:UBC 0 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 3060 :UNC 0 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 3061 :UBC 0 : break;
6732 tgl@sss.pgh.pa.us 3062 :CBC 1524 : case DCH_J:
7 tgl@sss.pgh.pa.us 3063 :GNC 1524 : DCH_EMITF("%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
300 peter@eisentraut.org 3064 [ + + ]:CBC 1524 : if (IS_SUFFIX_THth(n->suffix))
7 tgl@sss.pgh.pa.us 3065 :GNC 508 : str_numth(out, field_start, SUFFIX_TH_TYPE(n->suffix));
6732 tgl@sss.pgh.pa.us 3066 :CBC 1524 : break;
3067 : : }
3068 : : }
3069 : :
3070 : : #undef DCH_EMITF
3071 : : #undef DCH_EMITS
3072 : : #undef DCH_EMITC
3073 : 6308 : }
3074 : :
3075 : : /*
3076 : : * Process the string 'in' as denoted by the array of FormatNodes 'node[]'.
3077 : : * The TmFromChar struct pointed to by 'out' is populated with the results.
3078 : : *
3079 : : * 'collid' identifies the collation to use, if needed.
3080 : : * 'std' specifies standard parsing mode.
3081 : : *
3082 : : * If escontext points to an ErrorSaveContext, data errors will be reported
3083 : : * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
3084 : : * whether an error occurred. Otherwise, errors are thrown.
3085 : : *
3086 : : * Note: we currently don't have any to_interval() function, so there
3087 : : * is no need here for INVALID_FOR_INTERVAL checks.
3088 : : */
3089 : : static void
2368 3090 : 26989 : DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
3091 : : Oid collid, bool std, Node *escontext)
3092 : : {
3093 : : FormatNode *n;
3094 : : const char *s;
3095 : : int len,
3096 : : value;
2528 akorotkov@postgresql 3097 : 26989 : bool fx_mode = std;
3098 : :
3099 : : /* number of extra skipped characters (more than given in format string) */
2909 3100 : 26989 : int extra_skip = 0;
3101 : :
3102 : : /* cache localized days and months */
2368 tgl@sss.pgh.pa.us 3103 : 26989 : cache_locale_time();
3104 : :
6732 3105 [ + + + + ]: 162275 : for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
3106 : : {
3107 : : /*
3108 : : * Ignore spaces at the beginning of the string and before fields when
3109 : : * not in FX (fixed width) mode.
3110 : : */
2909 akorotkov@postgresql 3111 [ + + + + : 149581 : if (!fx_mode && (n->type != NODE_TYPE_ACTION || n->key->id != DCH_FX) &&
+ + ]
3112 [ + + + + ]: 6217 : (n->type == NODE_TYPE_ACTION || n == node))
3113 : : {
3114 [ + - + + ]: 3509 : while (*s != '\0' && isspace((unsigned char) *s))
3115 : : {
3116 : 56 : s++;
3117 : 56 : extra_skip++;
3118 : : }
3119 : : }
3120 : :
3121 [ + + + + ]: 149581 : if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
3122 : : {
2528 3123 [ + + ]: 64878 : if (std)
3124 : : {
3125 : : /*
3126 : : * Standard mode requires strict matching between format
3127 : : * string separators/spaces and input string.
3128 : : */
3129 [ + - - + ]: 62386 : Assert(n->character[0] && !n->character[1]);
3130 : :
3131 [ + + ]: 62386 : if (*s == n->character[0])
3132 : 50494 : s++;
3133 : : else
1357 tgl@sss.pgh.pa.us 3134 [ - + ]: 20750 : ereturn(escontext,,
3135 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3136 : : errmsg("unmatched format separator \"%c\"",
3137 : : n->character[0])));
3138 : : }
2528 akorotkov@postgresql 3139 [ + + ]: 2492 : else if (!fx_mode)
3140 : : {
3141 : : /*
3142 : : * In non FX (fixed format) mode one format string space or
3143 : : * separator match to one space or separator in input string.
3144 : : * Or match nothing if there is no space or separator in the
3145 : : * current position of input string.
3146 : : */
2909 3147 : 2476 : extra_skip--;
3148 [ + + + + ]: 2476 : if (isspace((unsigned char) *s) || is_separator_char(s))
3149 : : {
3150 : 1772 : s++;
3151 : 1772 : extra_skip++;
3152 : : }
3153 : : }
3154 : : else
3155 : : {
3156 : : /*
3157 : : * In FX mode, on format string space or separator we consume
3158 : : * exactly one character from input string. Notice we don't
3159 : : * insist that the consumed character match the format's
3160 : : * character.
3161 : : */
232 tmunro@postgresql.or 3162 : 16 : s += pg_mblen_cstr(s);
3163 : : }
2909 akorotkov@postgresql 3164 : 52986 : continue;
3165 : : }
3166 [ + + ]: 84703 : else if (n->type != NODE_TYPE_ACTION)
3167 : : {
3168 : : /*
3169 : : * Text character, so consume one character from input string.
3170 : : * Notice we don't insist that the consumed character match the
3171 : : * format's character.
3172 : : */
2898 3173 [ + + ]: 2126 : if (!fx_mode)
3174 : : {
3175 : : /*
3176 : : * In non FX mode we might have skipped some extra characters
3177 : : * (more than specified in format string) before. In this
3178 : : * case we don't skip input string character, because it might
3179 : : * be part of field.
3180 : : */
3181 [ + + ]: 292 : if (extra_skip > 0)
3182 : 16 : extra_skip--;
3183 : : else
232 tmunro@postgresql.or 3184 : 276 : s += pg_mblen_cstr(s);
3185 : : }
3186 : : else
3187 : : {
3188 : 1834 : int chlen = pg_mblen_cstr(s);
3189 : :
3190 : : /*
3191 : : * Standard mode requires strict match of format characters.
3192 : : */
2158 akorotkov@postgresql 3193 [ + - + - ]: 1834 : if (std && n->type == NODE_TYPE_CHAR &&
3194 [ + + ]: 1834 : strncmp(s, n->character, chlen) != 0)
1357 tgl@sss.pgh.pa.us 3195 [ + + ]: 1810 : ereturn(escontext,,
3196 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3197 : : errmsg("unmatched format character \"%s\"",
3198 : : n->character)));
3199 : :
2158 akorotkov@postgresql 3200 : 24 : s += chlen;
3201 : : }
6732 tgl@sss.pgh.pa.us 3202 : 316 : continue;
3203 : : }
3204 : :
1357 3205 [ - + ]: 82577 : if (!from_char_set_mode(out, n->key->date_mode, escontext))
1357 tgl@sss.pgh.pa.us 3206 :UBC 0 : return;
3207 : :
6732 tgl@sss.pgh.pa.us 3208 [ + + + + :CBC 82573 : switch (n->key->id)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
- ]
3209 : : {
3210 : 8 : case DCH_FX:
3211 : 8 : fx_mode = true;
3212 : 8 : break;
3213 : 8 : case DCH_A_M:
3214 : : case DCH_P_M:
3215 : : case DCH_a_m:
3216 : : case DCH_p_m:
1357 3217 [ - + ]: 8 : if (!from_char_seq_search(&value, &s, ampm_strings_long,
3218 : : NULL, InvalidOid,
3219 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3220 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3221 [ - + ]:CBC 8 : if (!from_char_set_int(&out->pm, value % 2, n, escontext))
1357 tgl@sss.pgh.pa.us 3222 :UBC 0 : return;
300 peter@eisentraut.org 3223 :CBC 8 : out->clock_12_hour = true;
6732 tgl@sss.pgh.pa.us 3224 : 8 : break;
6410 bruce@momjian.us 3225 : 8 : case DCH_AM:
3226 : : case DCH_PM:
3227 : : case DCH_am:
3228 : : case DCH_pm:
1357 tgl@sss.pgh.pa.us 3229 [ - + ]: 8 : if (!from_char_seq_search(&value, &s, ampm_strings,
3230 : : NULL, InvalidOid,
3231 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3232 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3233 [ - + ]:CBC 8 : if (!from_char_set_int(&out->pm, value % 2, n, escontext))
1357 tgl@sss.pgh.pa.us 3234 :UBC 0 : return;
300 peter@eisentraut.org 3235 :CBC 8 : out->clock_12_hour = true;
6732 tgl@sss.pgh.pa.us 3236 : 8 : break;
3237 : 96 : case DCH_HH:
3238 : : case DCH_HH12:
1357 3239 [ - + ]: 96 : if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3240 :UBC 0 : return;
300 peter@eisentraut.org 3241 :CBC 96 : out->clock_12_hour = true;
3765 tgl@sss.pgh.pa.us 3242 [ - + - - : 96 : SKIP_THth(s, n->suffix);
- - ]
6410 bruce@momjian.us 3243 : 96 : break;
6732 tgl@sss.pgh.pa.us 3244 : 19808 : case DCH_HH24:
1357 3245 [ + + ]: 19808 : if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
3246 : 96 : return;
3765 3247 [ - + - - : 19708 : SKIP_THth(s, n->suffix);
- - ]
6732 3248 : 19708 : break;
3249 : 11492 : case DCH_MI:
1357 3250 [ - + ]: 11492 : if (from_char_parse_int(&out->mi, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3251 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3252 [ - + - - :CBC 11492 : SKIP_THth(s, n->suffix);
- - ]
6732 3253 : 11492 : break;
3254 : 10568 : case DCH_SS:
1357 3255 [ - + ]: 10568 : if (from_char_parse_int(&out->ss, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3256 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3257 [ - + - - :CBC 10568 : SKIP_THth(s, n->suffix);
- - ]
6732 3258 : 10568 : break;
6286 bruce@momjian.us 3259 : 8 : case DCH_MS: /* millisecond */
1357 tgl@sss.pgh.pa.us 3260 : 8 : len = from_char_parse_int_len(&out->ms, &s, 3, n, escontext);
3261 [ - + ]: 8 : if (len < 0)
1357 tgl@sss.pgh.pa.us 3262 :UBC 0 : return;
3263 : :
3264 : : /*
3265 : : * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
3266 : : */
6559 tgl@sss.pgh.pa.us 3267 [ + - ]:CBC 16 : out->ms *= len == 1 ? 100 :
3268 [ - + ]: 8 : len == 2 ? 10 : 1;
3269 : :
3765 3270 [ - + - - : 8 : SKIP_THth(s, n->suffix);
- - ]
6732 3271 : 8 : break;
2537 akorotkov@postgresql 3272 : 172 : case DCH_FF1:
3273 : : case DCH_FF2:
3274 : : case DCH_FF3:
3275 : : case DCH_FF4:
3276 : : case DCH_FF5:
3277 : : case DCH_FF6:
3278 : 172 : out->ff = n->key->id - DCH_FF1 + 1;
3279 : : pg_fallthrough;
6286 bruce@momjian.us 3280 : 888 : case DCH_US: /* microsecond */
2537 akorotkov@postgresql 3281 : 888 : len = from_char_parse_int_len(&out->us, &s,
3282 [ + + ]: 888 : n->key->id == DCH_US ? 6 :
1357 tgl@sss.pgh.pa.us 3283 : 172 : out->ff, n, escontext);
3284 [ - + ]: 888 : if (len < 0)
1357 tgl@sss.pgh.pa.us 3285 :UBC 0 : return;
3286 : :
6559 tgl@sss.pgh.pa.us 3287 [ + + ]:CBC 1724 : out->us *= len == 1 ? 100000 :
3288 [ + + ]: 1648 : len == 2 ? 10000 :
3289 [ + + ]: 1016 : len == 3 ? 1000 :
3290 [ + + ]: 304 : len == 4 ? 100 :
3291 [ + + ]: 100 : len == 5 ? 10 : 1;
3292 : :
3765 3293 [ - + - - : 888 : SKIP_THth(s, n->suffix);
- - ]
6732 3294 : 888 : break;
3295 : 16 : case DCH_SSSS:
1357 3296 [ - + ]: 16 : if (from_char_parse_int(&out->ssss, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3297 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3298 [ - + - - :CBC 16 : SKIP_THth(s, n->suffix);
- - ]
6732 3299 : 16 : break;
3300 : 2374 : case DCH_tz:
3301 : : case DCH_TZ:
3302 : : {
3303 : : int tzlen;
3304 : :
945 3305 : 2374 : tzlen = DecodeTimezoneAbbrevPrefix(s,
3306 : : &out->gmtoffset,
3307 : : &out->tzp);
3308 [ + + ]: 2374 : if (tzlen > 0)
3309 : : {
3310 : 24 : out->has_tz = true;
3311 : : /* we only need the zone abbrev for DYNTZ case */
3312 [ + + ]: 24 : if (out->tzp)
3313 : 4 : out->abbrev = pnstrdup(s, tzlen);
3314 : 24 : out->tzsign = 0; /* drop any earlier TZH/TZM info */
3315 : 24 : s += tzlen;
3316 : 24 : break;
3317 : : }
3318 [ + + ]: 2350 : else if (isalpha((unsigned char) *s))
3319 : : {
3320 : : /*
3321 : : * It doesn't match any abbreviation, but it starts
3322 : : * with a letter. OF format certainly won't succeed;
3323 : : * assume it's a misspelled abbreviation and complain
3324 : : * accordingly.
3325 : : */
3326 [ + - ]: 4 : ereturn(escontext,,
3327 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3328 : : errmsg("invalid value \"%s\" for \"%s\"", s, n->key->name),
3329 : : errdetail("Time zone abbreviation is not recognized.")));
3330 : : }
3331 : : /* otherwise parse it like OF */
3332 : : }
3333 : : pg_fallthrough;
3334 : : case DCH_OF:
3335 : : /* OF is equivalent to TZH or TZH:TZM */
3336 : : /* see TZH comments below */
3337 [ + + + + : 2362 : if (*s == '+' || *s == '-' || *s == ' ')
+ + ]
3338 : : {
3339 [ + + ]: 2122 : out->tzsign = *s == '-' ? -1 : +1;
3340 : 2122 : s++;
3341 : : }
3342 : : else
3343 : : {
3344 [ + + + + ]: 240 : if (extra_skip > 0 && *(s - 1) == '-')
3345 : 8 : out->tzsign = -1;
3346 : : else
3347 : 232 : out->tzsign = +1;
3348 : : }
3349 [ + + ]: 2362 : if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
3350 : 212 : return;
3351 [ + + ]: 2142 : if (*s == ':')
3352 : : {
3353 : 222 : s++;
3354 [ - + ]: 222 : if (from_char_parse_int_len(&out->tzm, &s, 2, n,
3355 : : escontext) < 0)
945 tgl@sss.pgh.pa.us 3356 :UBC 0 : return;
3357 : : }
3475 tgl@sss.pgh.pa.us 3358 :CBC 2138 : break;
3152 andrew@dunslane.net 3359 : 516 : case DCH_TZH:
3360 : :
3361 : : /*
3362 : : * Value of TZH might be negative. And the issue is that we
3363 : : * might swallow minus sign as the separator. So, if we have
3364 : : * skipped more characters than specified in the format
3365 : : * string, then we consider prepending last skipped minus to
3366 : : * TZH.
3367 : : */
3368 [ + + + + : 516 : if (*s == '+' || *s == '-' || *s == ' ')
- + ]
3369 : : {
2909 akorotkov@postgresql 3370 [ + + ]: 492 : out->tzsign = *s == '-' ? -1 : +1;
3152 andrew@dunslane.net 3371 : 492 : s++;
3372 : : }
3373 : : else
3374 : : {
2909 akorotkov@postgresql 3375 [ + + + + ]: 24 : if (extra_skip > 0 && *(s - 1) == '-')
3376 : 12 : out->tzsign = -1;
3377 : : else
3378 : 12 : out->tzsign = +1;
3379 : : }
3380 : :
1357 tgl@sss.pgh.pa.us 3381 [ - + ]: 516 : if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3382 :UBC 0 : return;
3152 andrew@dunslane.net 3383 :CBC 516 : break;
3384 : 52 : case DCH_TZM:
3385 : : /* assign positive timezone sign if TZH was not seen before */
3386 [ + + ]: 52 : if (!out->tzsign)
3387 : 4 : out->tzsign = +1;
1357 tgl@sss.pgh.pa.us 3388 [ - + ]: 52 : if (from_char_parse_int_len(&out->tzm, &s, 2, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3389 :UBC 0 : return;
3152 andrew@dunslane.net 3390 :CBC 52 : break;
6732 tgl@sss.pgh.pa.us 3391 : 8 : case DCH_A_D:
3392 : : case DCH_B_C:
3393 : : case DCH_a_d:
3394 : : case DCH_b_c:
1357 3395 [ - + ]: 8 : if (!from_char_seq_search(&value, &s, adbc_strings_long,
3396 : : NULL, InvalidOid,
3397 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3398 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3399 [ - + ]:CBC 8 : if (!from_char_set_int(&out->bc, value % 2, n, escontext))
1357 tgl@sss.pgh.pa.us 3400 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3401 :CBC 8 : break;
6410 bruce@momjian.us 3402 : 24 : case DCH_AD:
3403 : : case DCH_BC:
3404 : : case DCH_ad:
3405 : : case DCH_bc:
1357 tgl@sss.pgh.pa.us 3406 [ - + ]: 24 : if (!from_char_seq_search(&value, &s, adbc_strings,
3407 : : NULL, InvalidOid,
3408 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3409 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3410 [ - + ]:CBC 24 : if (!from_char_set_int(&out->bc, value % 2, n, escontext))
1357 tgl@sss.pgh.pa.us 3411 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3412 :CBC 24 : break;
3413 : 12 : case DCH_MONTH:
3414 : : case DCH_Month:
3415 : : case DCH_month:
1357 3416 [ - + - + ]: 12 : if (!from_char_seq_search(&value, &s, months_full,
300 peter@eisentraut.org 3417 : 12 : IS_SUFFIX_TM(n->suffix) ? localized_full_months : NULL,
3418 : : collid,
3419 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3420 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3421 [ - + ]:CBC 12 : if (!from_char_set_int(&out->mm, value + 1, n, escontext))
1357 tgl@sss.pgh.pa.us 3422 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3423 :CBC 12 : break;
3424 : 76 : case DCH_MON:
3425 : : case DCH_Mon:
3426 : : case DCH_mon:
1357 3427 [ - + - + ]: 76 : if (!from_char_seq_search(&value, &s, months,
300 peter@eisentraut.org 3428 : 76 : IS_SUFFIX_TM(n->suffix) ? localized_abbrev_months : NULL,
3429 : : collid,
3430 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3431 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3432 [ - + ]:CBC 68 : if (!from_char_set_int(&out->mm, value + 1, n, escontext))
1357 tgl@sss.pgh.pa.us 3433 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3434 :CBC 64 : break;
3435 : 11448 : case DCH_MM:
1357 3436 [ - + ]: 11448 : if (from_char_parse_int(&out->mm, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3437 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3438 [ - + - - :CBC 11436 : SKIP_THth(s, n->suffix);
- - ]
6732 3439 : 11436 : break;
3440 : 4 : case DCH_DAY:
3441 : : case DCH_Day:
3442 : : case DCH_day:
1357 3443 [ - + - + ]: 4 : if (!from_char_seq_search(&value, &s, days,
300 peter@eisentraut.org 3444 : 4 : IS_SUFFIX_TM(n->suffix) ? localized_full_days : NULL,
3445 : : collid,
3446 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3447 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3448 [ - + ]:CBC 4 : if (!from_char_set_int(&out->d, value, n, escontext))
1357 tgl@sss.pgh.pa.us 3449 :UBC 0 : return;
5106 bruce@momjian.us 3450 :CBC 4 : out->d++;
6732 tgl@sss.pgh.pa.us 3451 : 4 : break;
3452 : 12 : case DCH_DY:
3453 : : case DCH_Dy:
3454 : : case DCH_dy:
1357 3455 [ - + - + ]: 12 : if (!from_char_seq_search(&value, &s, days_short,
300 peter@eisentraut.org 3456 : 12 : IS_SUFFIX_TM(n->suffix) ? localized_abbrev_days : NULL,
3457 : : collid,
3458 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3459 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3460 [ - + ]:CBC 12 : if (!from_char_set_int(&out->d, value, n, escontext))
1357 tgl@sss.pgh.pa.us 3461 :UBC 0 : return;
5106 bruce@momjian.us 3462 :CBC 12 : out->d++;
6732 tgl@sss.pgh.pa.us 3463 : 12 : break;
3464 : 24 : case DCH_DDD:
1357 3465 [ - + ]: 24 : if (from_char_parse_int(&out->ddd, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3466 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3467 [ - + - - :CBC 24 : SKIP_THth(s, n->suffix);
- - ]
6559 3468 : 24 : break;
6732 3469 : 4 : case DCH_IDDD:
1357 3470 [ - + ]: 4 : if (from_char_parse_int_len(&out->ddd, &s, 3, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3471 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3472 [ - + - - :CBC 4 : SKIP_THth(s, n->suffix);
- - ]
6732 3473 : 4 : break;
3474 : 11489 : case DCH_DD:
1357 3475 [ - + ]: 11489 : if (from_char_parse_int(&out->dd, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3476 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3477 [ - + - - :CBC 11480 : SKIP_THth(s, n->suffix);
- - ]
6732 3478 : 11480 : break;
3479 : 8 : case DCH_D:
1357 3480 [ - + ]: 8 : if (from_char_parse_int(&out->d, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3481 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3482 [ - + - - :CBC 8 : SKIP_THth(s, n->suffix);
- - ]
6559 3483 : 8 : break;
6732 3484 : 16 : case DCH_ID:
1357 3485 [ - + ]: 16 : if (from_char_parse_int_len(&out->d, &s, 1, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3486 :UBC 0 : return;
3487 : : /* Shift numbering to match Gregorian where Sunday = 1 */
5106 bruce@momjian.us 3488 [ + - ]:CBC 16 : if (++out->d > 7)
3489 : 16 : out->d = 1;
3765 tgl@sss.pgh.pa.us 3490 [ - + - - : 16 : SKIP_THth(s, n->suffix);
- - ]
6732 3491 : 16 : break;
3492 : 24 : case DCH_WW:
3493 : : case DCH_IW:
1357 3494 [ - + ]: 24 : if (from_char_parse_int(&out->ww, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3495 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3496 [ - + - - :CBC 24 : SKIP_THth(s, n->suffix);
- - ]
6732 3497 : 24 : break;
3498 : 4 : case DCH_Q:
3499 : :
3500 : : /*
3501 : : * We ignore 'Q' when converting to date because it is unclear
3502 : : * which date in the quarter to use, and some people specify
3503 : : * both quarter and month, so if it was honored it might
3504 : : * conflict with the supplied month. That is also why we don't
3505 : : * throw an error.
3506 : : *
3507 : : * We still parse the source string for an integer, but it
3508 : : * isn't stored anywhere in 'out'.
3509 : : */
1357 3510 [ - + ]: 4 : if (from_char_parse_int((int *) NULL, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3511 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3512 [ - + - - :CBC 4 : SKIP_THth(s, n->suffix);
- - ]
6732 3513 : 4 : break;
3514 : 20 : case DCH_CC:
1357 3515 [ - + ]: 20 : if (from_char_parse_int(&out->cc, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3516 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3517 [ - + - - :CBC 20 : SKIP_THth(s, n->suffix);
- - ]
6732 3518 : 20 : break;
3519 : 8 : case DCH_Y_YYY:
3520 : : {
3521 : : int matched,
3522 : : years,
3523 : : millennia,
3524 : : nch;
3525 : :
3737 stark@mit.edu 3526 : 8 : matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
3765 tgl@sss.pgh.pa.us 3527 [ - + ]: 8 : if (matched < 2)
1357 tgl@sss.pgh.pa.us 3528 [ # # ]:UBC 0 : ereturn(escontext,,
3529 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3530 : : errmsg("invalid value \"%s\" for \"%s\"", s, "Y,YYY")));
3531 : :
3532 : : /* years += (millennia * 1000); */
626 nathan@postgresql.or 3533 [ + + - + ]:CBC 12 : if (pg_mul_s32_overflow(millennia, 1000, &millennia) ||
3534 : 4 : pg_add_s32_overflow(years, millennia, &years))
3535 [ + - ]: 4 : ereturn(escontext,,
3536 : : (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
3537 : : errmsg("value for \"%s\" in source string is out of range", "Y,YYY")));
3538 : :
1357 tgl@sss.pgh.pa.us 3539 [ - + ]: 4 : if (!from_char_set_int(&out->year, years, n, escontext))
1357 tgl@sss.pgh.pa.us 3540 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3541 :CBC 4 : out->yysz = 4;
3765 3542 : 4 : s += nch;
3543 [ + - + - : 4 : SKIP_THth(s, n->suffix);
+ - ]
3544 : : }
6732 3545 : 4 : break;
6559 3546 : 13462 : case DCH_YYYY:
3547 : : case DCH_IYYY:
1357 3548 [ + + ]: 13462 : if (from_char_parse_int(&out->year, &s, n, escontext) < 0)
3549 : 216 : return;
6559 3550 : 13238 : out->yysz = 4;
3765 3551 [ - + - - : 13238 : SKIP_THth(s, n->suffix);
- - ]
6559 3552 : 13238 : break;
6732 3553 : 8 : case DCH_YYY:
3554 : : case DCH_IYY:
1357 3555 : 8 : len = from_char_parse_int(&out->year, &s, n, escontext);
3556 [ - + ]: 8 : if (len < 0)
1357 tgl@sss.pgh.pa.us 3557 :UBC 0 : return;
2528 akorotkov@postgresql 3558 [ + - ]:CBC 8 : if (len < 4)
5468 bruce@momjian.us 3559 : 8 : out->year = adjust_partial_year_to_2020(out->year);
6559 tgl@sss.pgh.pa.us 3560 : 8 : out->yysz = 3;
3765 3561 [ - + - - : 8 : SKIP_THth(s, n->suffix);
- - ]
6732 3562 : 8 : break;
3563 : 40 : case DCH_YY:
3564 : : case DCH_IY:
1357 3565 : 40 : len = from_char_parse_int(&out->year, &s, n, escontext);
3566 [ - + ]: 40 : if (len < 0)
1357 tgl@sss.pgh.pa.us 3567 :UBC 0 : return;
2528 akorotkov@postgresql 3568 [ + - ]:CBC 40 : if (len < 4)
5468 bruce@momjian.us 3569 : 40 : out->year = adjust_partial_year_to_2020(out->year);
6559 tgl@sss.pgh.pa.us 3570 : 40 : out->yysz = 2;
3765 3571 [ - + - - : 40 : SKIP_THth(s, n->suffix);
- - ]
6732 3572 : 40 : break;
3573 : 8 : case DCH_Y:
3574 : : case DCH_I:
1357 3575 : 8 : len = from_char_parse_int(&out->year, &s, n, escontext);
3576 [ - + ]: 8 : if (len < 0)
1357 tgl@sss.pgh.pa.us 3577 :UBC 0 : return;
2528 akorotkov@postgresql 3578 [ + - ]:CBC 8 : if (len < 4)
5468 bruce@momjian.us 3579 : 8 : out->year = adjust_partial_year_to_2020(out->year);
6559 tgl@sss.pgh.pa.us 3580 : 8 : out->yysz = 1;
3765 3581 [ - + - - : 8 : SKIP_THth(s, n->suffix);
- - ]
6732 3582 : 8 : break;
3583 : 4 : case DCH_RM:
3584 : : case DCH_rm:
1357 3585 [ - + ]: 4 : if (!from_char_seq_search(&value, &s, rm_months_lower,
3586 : : NULL, InvalidOid,
3587 : : n, escontext))
1357 tgl@sss.pgh.pa.us 3588 :UBC 0 : return;
1357 tgl@sss.pgh.pa.us 3589 [ - + ]:CBC 4 : if (!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
3590 : : escontext))
1357 tgl@sss.pgh.pa.us 3591 :UBC 0 : return;
6732 tgl@sss.pgh.pa.us 3592 :CBC 4 : break;
3593 : 8 : case DCH_W:
1357 3594 [ - + ]: 8 : if (from_char_parse_int(&out->w, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3595 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3596 [ - + - - :CBC 8 : SKIP_THth(s, n->suffix);
- - ]
6732 3597 : 8 : break;
3598 : 4 : case DCH_J:
1357 3599 [ - + ]: 4 : if (from_char_parse_int(&out->j, &s, n, escontext) < 0)
1357 tgl@sss.pgh.pa.us 3600 :UBC 0 : return;
3765 tgl@sss.pgh.pa.us 3601 [ - + - - :CBC 4 : SKIP_THth(s, n->suffix);
- - ]
6732 3602 : 4 : break;
3603 : : }
3604 : :
3605 : : /* Ignore all spaces after fields */
2909 akorotkov@postgresql 3606 [ + + ]: 81984 : if (!fx_mode)
3607 : : {
3608 : 3384 : extra_skip = 0;
3609 [ + + + + ]: 4180 : while (*s != '\0' && isspace((unsigned char) *s))
3610 : : {
3611 : 796 : s++;
3612 : 796 : extra_skip++;
3613 : : }
3614 : : }
3615 : : }
3616 : :
3617 : : /*
3618 : : * Standard parsing mode doesn't allow unmatched format patterns or
3619 : : * trailing characters in the input string.
3620 : : */
2528 3621 [ + + ]: 12694 : if (std)
3622 : : {
3623 [ + + ]: 12010 : if (n->type != NODE_TYPE_END)
1357 tgl@sss.pgh.pa.us 3624 [ + + ]: 4470 : ereturn(escontext,,
3625 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3626 : : errmsg("input string is too short for datetime format")));
3627 : :
2528 akorotkov@postgresql 3628 [ + + + + ]: 9602 : while (*s != '\0' && isspace((unsigned char) *s))
3629 : 2062 : s++;
3630 : :
3631 [ + + ]: 7540 : if (*s != '\0')
1357 tgl@sss.pgh.pa.us 3632 [ + + ]: 2086 : ereturn(escontext,,
3633 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3634 : : errmsg("trailing characters remain in input string after datetime format")));
3635 : : }
3636 : : }
3637 : :
3638 : : /*
3639 : : * The invariant for DCH cache entry management is that DCHCounter is equal
3640 : : * to the maximum age value among the existing entries, and we increment it
3641 : : * whenever an access occurs. If we approach overflow, deal with that by
3642 : : * halving all the age values, so that we retain a fairly accurate idea of
3643 : : * which entries are oldest.
3644 : : */
3645 : : static inline void
2872 3646 : 33968 : DCH_prevent_counter_overflow(void)
3647 : : {
3648 [ - + ]: 33968 : if (DCHCounter >= (INT_MAX - 1))
3649 : : {
2872 tgl@sss.pgh.pa.us 3650 [ # # ]:UBC 0 : for (int i = 0; i < n_DCHCache; i++)
3651 : 0 : DCHCache[i]->age >>= 1;
3652 : 0 : DCHCounter >>= 1;
3653 : : }
2872 tgl@sss.pgh.pa.us 3654 :CBC 33968 : }
3655 : :
3656 : : /*
3657 : : * Get mask of date/time/zone components present in format nodes.
3658 : : */
3659 : : static int
1357 3660 : 5498 : DCH_datetime_type(FormatNode *node)
3661 : : {
2528 akorotkov@postgresql 3662 : 5498 : int flags = 0;
3663 : :
303 peter@eisentraut.org 3664 [ + + ]: 50574 : for (FormatNode *n = node; n->type != NODE_TYPE_END; n++)
3665 : : {
2528 akorotkov@postgresql 3666 [ + + ]: 45076 : if (n->type != NODE_TYPE_ACTION)
3667 : 18726 : continue;
3668 : :
3669 [ - + + + : 26350 : switch (n->key->id)
- ]
3670 : : {
2528 akorotkov@postgresql 3671 :UBC 0 : case DCH_FX:
3672 : 0 : break;
2528 akorotkov@postgresql 3673 :CBC 13540 : case DCH_A_M:
3674 : : case DCH_P_M:
3675 : : case DCH_a_m:
3676 : : case DCH_p_m:
3677 : : case DCH_AM:
3678 : : case DCH_PM:
3679 : : case DCH_am:
3680 : : case DCH_pm:
3681 : : case DCH_HH:
3682 : : case DCH_HH12:
3683 : : case DCH_HH24:
3684 : : case DCH_MI:
3685 : : case DCH_SS:
3686 : : case DCH_MS: /* millisecond */
3687 : : case DCH_US: /* microsecond */
3688 : : case DCH_FF1:
3689 : : case DCH_FF2:
3690 : : case DCH_FF3:
3691 : : case DCH_FF4:
3692 : : case DCH_FF5:
3693 : : case DCH_FF6:
3694 : : case DCH_SSSS:
3695 : 13540 : flags |= DCH_TIMED;
3696 : 13540 : break;
3697 : 2682 : case DCH_tz:
3698 : : case DCH_TZ:
3699 : : case DCH_OF:
3700 : : case DCH_TZH:
3701 : : case DCH_TZM:
3702 : 2682 : flags |= DCH_ZONED;
3703 : 2682 : break;
3704 : 10128 : case DCH_A_D:
3705 : : case DCH_B_C:
3706 : : case DCH_a_d:
3707 : : case DCH_b_c:
3708 : : case DCH_AD:
3709 : : case DCH_BC:
3710 : : case DCH_ad:
3711 : : case DCH_bc:
3712 : : case DCH_MONTH:
3713 : : case DCH_Month:
3714 : : case DCH_month:
3715 : : case DCH_MON:
3716 : : case DCH_Mon:
3717 : : case DCH_mon:
3718 : : case DCH_MM:
3719 : : case DCH_DAY:
3720 : : case DCH_Day:
3721 : : case DCH_day:
3722 : : case DCH_DY:
3723 : : case DCH_Dy:
3724 : : case DCH_dy:
3725 : : case DCH_DDD:
3726 : : case DCH_IDDD:
3727 : : case DCH_DD:
3728 : : case DCH_D:
3729 : : case DCH_ID:
3730 : : case DCH_WW:
3731 : : case DCH_Q:
3732 : : case DCH_CC:
3733 : : case DCH_Y_YYY:
3734 : : case DCH_YYYY:
3735 : : case DCH_IYYY:
3736 : : case DCH_YYY:
3737 : : case DCH_IYY:
3738 : : case DCH_YY:
3739 : : case DCH_IY:
3740 : : case DCH_Y:
3741 : : case DCH_I:
3742 : : case DCH_RM:
3743 : : case DCH_rm:
3744 : : case DCH_W:
3745 : : case DCH_J:
3746 : 10128 : flags |= DCH_DATED;
3747 : 10128 : break;
3748 : : }
3749 : : }
3750 : :
3751 : 5498 : return flags;
3752 : : }
3753 : :
3754 : : /* select a DCHCacheEntry to hold the given format picture */
3755 : : static DCHCacheEntry *
3756 : 623 : DCH_cache_getnew(const char *str, bool std)
3757 : : {
3758 : : DCHCacheEntry *ent;
3759 : :
3760 : : /* Ensure we can advance DCHCounter below */
2872 tgl@sss.pgh.pa.us 3761 : 623 : DCH_prevent_counter_overflow();
3762 : :
3763 : : /*
3764 : : * If cache is full, remove oldest entry (or recycle first not-valid one)
3765 : : */
3620 3766 [ + + ]: 623 : if (n_DCHCache >= DCH_CACHE_ENTRIES)
3767 : : {
2872 3768 : 308 : DCHCacheEntry *old = DCHCache[0];
3769 : :
3770 : : #ifdef DEBUG_TO_FROM_CHAR
3771 : : elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
3772 : : #endif
3620 3773 [ + - ]: 308 : if (old->valid)
3774 : : {
2872 3775 [ + + ]: 6132 : for (int i = 1; i < DCH_CACHE_ENTRIES; i++)
3776 : : {
3777 : 5828 : ent = DCHCache[i];
3620 3778 [ + + ]: 5828 : if (!ent->valid)
3779 : : {
3780 : 4 : old = ent;
3781 : 4 : break;
3782 : : }
3783 [ + + ]: 5824 : if (ent->age < old->age)
3784 : 512 : old = ent;
3785 : : }
3786 : : }
3787 : : #ifdef DEBUG_TO_FROM_CHAR
3788 : : elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
3789 : : #endif
3790 : 308 : old->valid = false;
2208 peter@eisentraut.org 3791 : 308 : strlcpy(old->str, str, DCH_CACHE_SIZE + 1);
9660 bruce@momjian.us 3792 : 308 : old->age = (++DCHCounter);
3793 : : /* caller is expected to fill format, then set valid */
3794 : 308 : return old;
3795 : : }
3796 : : else
3797 : : {
3798 : : #ifdef DEBUG_TO_FROM_CHAR
3799 : : elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
3800 : : #endif
2872 tgl@sss.pgh.pa.us 3801 [ - + ]: 315 : Assert(DCHCache[n_DCHCache] == NULL);
3802 : 315 : DCHCache[n_DCHCache] = ent = (DCHCacheEntry *)
3803 : 315 : MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
3620 3804 : 315 : ent->valid = false;
2208 peter@eisentraut.org 3805 : 315 : strlcpy(ent->str, str, DCH_CACHE_SIZE + 1);
2528 akorotkov@postgresql 3806 : 315 : ent->std = std;
9660 bruce@momjian.us 3807 : 315 : ent->age = (++DCHCounter);
3808 : : /* caller is expected to fill format, then set valid */
3809 : 315 : ++n_DCHCache;
3810 : 315 : return ent;
3811 : : }
3812 : : }
3813 : :
3814 : : /* look for an existing DCHCacheEntry matching the given format picture */
3815 : : static DCHCacheEntry *
2528 akorotkov@postgresql 3816 : 33345 : DCH_cache_search(const char *str, bool std)
3817 : : {
3818 : : /* Ensure we can advance DCHCounter below */
2872 tgl@sss.pgh.pa.us 3819 : 33345 : DCH_prevent_counter_overflow();
3820 : :
3821 [ + + ]: 176868 : for (int i = 0; i < n_DCHCache; i++)
3822 : : {
3823 : 176245 : DCHCacheEntry *ent = DCHCache[i];
3824 : :
2528 akorotkov@postgresql 3825 [ + + + + : 176245 : if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
+ - ]
3826 : : {
9660 bruce@momjian.us 3827 : 32722 : ent->age = (++DCHCounter);
3828 : 32722 : return ent;
3829 : : }
3830 : : }
3831 : :
8268 neilc@samurai.com 3832 : 623 : return NULL;
3833 : : }
3834 : :
3835 : : /* Find or create a DCHCacheEntry for the given format picture */
3836 : : static DCHCacheEntry *
2528 akorotkov@postgresql 3837 : 33345 : DCH_cache_fetch(const char *str, bool std)
3838 : : {
3839 : : DCHCacheEntry *ent;
3840 : :
3841 [ + + ]: 33345 : if ((ent = DCH_cache_search(str, std)) == NULL)
3842 : : {
3843 : : /*
3844 : : * Not in the cache, must run parser and save a new format-picture to
3845 : : * the cache. Do not mark the cache entry valid until parsing
3846 : : * succeeds.
3847 : : */
3848 : 623 : ent = DCH_cache_getnew(str, std);
3849 : :
3850 [ + + ]: 623 : parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
3851 : : DCH_FLAG | (std ? STD_FLAG : 0), NULL);
3852 : :
3620 tgl@sss.pgh.pa.us 3853 : 619 : ent->valid = true;
3854 : : }
3855 : 33341 : return ent;
3856 : : }
3857 : :
3858 : : /*
3859 : : * Format a date/time or interval into a string according to fmt.
3860 : : * We parse fmt into a list of FormatNodes. This is then passed to DCH_to_char
3861 : : * for formatting.
3862 : : */
3863 : : static text *
7 tgl@sss.pgh.pa.us 3864 :GNC 6308 : datetime_to_char_body(const TmToChar *tmtc, const text *fmt,
3865 : : bool is_interval, Oid collid)
3866 : : {
3867 : : FormatNode *format;
3868 : : char *fmt_str;
3869 : : size_t fmt_len;
3870 : : bool incache;
3871 : : StringInfoData result;
3872 : :
3873 : : /*
3874 : : * Convert fmt to C string
3875 : : */
6729 tgl@sss.pgh.pa.us 3876 :CBC 6308 : fmt_str = text_to_cstring(fmt);
3877 : 6308 : fmt_len = strlen(fmt_str);
3878 : :
3879 : : /*
3880 : : * Create workspace to hold result. We'll use result.data directly as the
3881 : : * returned TEXT datum, so leave enough room for the varlena header.
3882 : : * Temporarily fill that area with spaces; that's not really necessary but
3883 : : * it eases debugging by ensuring the result string is always printable.
3884 : : */
7 tgl@sss.pgh.pa.us 3885 :GNC 6308 : initStringInfo(&result);
3886 : 6308 : enlargeStringInfo(&result, VARHDRSZ); /* just pro-forma */
3887 : 6308 : memset(result.data, ' ', VARHDRSZ);
3888 : 6308 : result.len = VARHDRSZ;
3889 : 6308 : result.data[VARHDRSZ] = '\0'; /* maintain StringInfo's invariant */
3890 : :
8394 tgl@sss.pgh.pa.us 3891 [ - + ]:CBC 6308 : if (fmt_len > DCH_CACHE_SIZE)
3892 : : {
3893 : : /*
3894 : : * Allocate new memory if format picture is bigger than static cache
3895 : : * and do not use cache (call parser always)
3896 : : */
3298 peter_e@gmx.net 3897 :UBC 0 : incache = false;
3898 : :
108 nathan@postgresql.or 3899 : 0 : format = palloc_array(FormatNode, fmt_len + 1);
3900 : :
8394 tgl@sss.pgh.pa.us 3901 : 0 : parse_format(format, fmt_str, DCH_keywords,
3902 : : DCH_suff, DCH_index, DCH_FLAG, NULL);
3903 : : }
3904 : : else
3905 : : {
3906 : : /*
3907 : : * Use cache buffers
3908 : : */
2528 akorotkov@postgresql 3909 :CBC 6308 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
3910 : :
3298 peter_e@gmx.net 3911 : 6308 : incache = true;
9633 bruce@momjian.us 3912 : 6308 : format = ent->format;
3913 : : }
3914 : :
3915 : : /* The real work is here */
7 tgl@sss.pgh.pa.us 3916 :GNC 6308 : DCH_to_char(format, is_interval, collid, tmtc, &result);
3917 : :
9121 bruce@momjian.us 3918 [ - + ]:CBC 6308 : if (!incache)
9711 bruce@momjian.us 3919 :UBC 0 : pfree(format);
3920 : :
8394 tgl@sss.pgh.pa.us 3921 :CBC 6308 : pfree(fmt_str);
3922 : :
3923 : : /* Insert the varlena header needed to make result a valid TEXT datum */
7 tgl@sss.pgh.pa.us 3924 :GNC 6308 : SET_VARSIZE(result.data, result.len);
3925 : :
3926 : 6308 : return (text *) result.data;
3927 : : }
3928 : :
3929 : : /****************************************************************************
3930 : : * Public routines
3931 : : ***************************************************************************/
3932 : :
3933 : : /*
3934 : : * TIMESTAMP to_char()
3935 : : */
3936 : : Datum
9121 bruce@momjian.us 3937 :CBC 3121 : timestamp_to_char(PG_FUNCTION_ARGS)
3938 : : {
9072 3939 : 3121 : Timestamp dt = PG_GETARG_TIMESTAMP(0);
3455 noah@leadboat.com 3940 : 3121 : text *fmt = PG_GETARG_TEXT_PP(1),
3941 : : *res;
3942 : : TmToChar tmtc;
3943 : : struct pg_tm tt;
3944 : : struct fmt_tm *tm;
3945 : : int thisdate;
3946 : :
3947 [ + - + + : 3121 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
+ + ]
9099 lockhart@fourpalms.o 3948 : 88 : PG_RETURN_NULL();
3949 : :
3950 : 3033 : ZERO_tmtc(&tmtc);
7616 tgl@sss.pgh.pa.us 3951 : 3033 : tm = tmtcTm(&tmtc);
3952 : :
1608 3953 [ - + ]: 3033 : if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
8432 tgl@sss.pgh.pa.us 3954 [ # # ]:UBC 0 : ereport(ERROR,
3955 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3956 : : errmsg("timestamp out of range")));
3957 : :
3958 : : /* calculate wday and yday, because timestamp2tm doesn't */
1607 tgl@sss.pgh.pa.us 3959 :CBC 3033 : thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
3960 : 3033 : tt.tm_wday = (thisdate + 1) % 7;
3961 : 3033 : tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
3962 : :
3963 : 3033 : COPY_tm(tm, &tt);
3964 : :
5679 peter_e@gmx.net 3965 [ - + ]: 3033 : if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
9099 lockhart@fourpalms.o 3966 :UBC 0 : PG_RETURN_NULL();
3967 : :
9099 lockhart@fourpalms.o 3968 :CBC 3033 : PG_RETURN_TEXT_P(res);
3969 : : }
3970 : :
3971 : : Datum
3972 : 3146 : timestamptz_to_char(PG_FUNCTION_ARGS)
3973 : : {
3974 : 3146 : TimestampTz dt = PG_GETARG_TIMESTAMP(0);
3455 noah@leadboat.com 3975 : 3146 : text *fmt = PG_GETARG_TEXT_PP(1),
3976 : : *res;
3977 : : TmToChar tmtc;
3978 : : int tz;
3979 : : struct pg_tm tt;
3980 : : struct fmt_tm *tm;
3981 : : int thisdate;
3982 : :
3983 [ + - + + : 3146 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
+ + ]
9121 bruce@momjian.us 3984 : 88 : PG_RETURN_NULL();
3985 : :
3986 : 3058 : ZERO_tmtc(&tmtc);
7616 tgl@sss.pgh.pa.us 3987 : 3058 : tm = tmtcTm(&tmtc);
3988 : :
1608 3989 [ - + ]: 3058 : if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
8432 tgl@sss.pgh.pa.us 3990 [ # # ]:UBC 0 : ereport(ERROR,
3991 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3992 : : errmsg("timestamp out of range")));
3993 : :
3994 : : /* calculate wday and yday, because timestamp2tm doesn't */
1607 tgl@sss.pgh.pa.us 3995 :CBC 3058 : thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
3996 : 3058 : tt.tm_wday = (thisdate + 1) % 7;
3997 : 3058 : tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
3998 : :
3999 : 3058 : COPY_tm(tm, &tt);
4000 : :
5679 peter_e@gmx.net 4001 [ - + ]: 3058 : if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
9121 bruce@momjian.us 4002 :UBC 0 : PG_RETURN_NULL();
4003 : :
9121 bruce@momjian.us 4004 :CBC 3058 : PG_RETURN_TEXT_P(res);
4005 : : }
4006 : :
4007 : :
4008 : : /*
4009 : : * INTERVAL to_char()
4010 : : */
4011 : : Datum
4012 : 225 : interval_to_char(PG_FUNCTION_ARGS)
4013 : : {
9072 4014 : 225 : Interval *it = PG_GETARG_INTERVAL_P(0);
3455 noah@leadboat.com 4015 : 225 : text *fmt = PG_GETARG_TEXT_PP(1),
4016 : : *res;
4017 : : TmToChar tmtc;
4018 : : struct fmt_tm *tm;
4019 : : struct pg_itm tt,
1608 tgl@sss.pgh.pa.us 4020 : 225 : *itm = &tt;
4021 : :
1017 dean.a.rasheed@gmail 4022 [ + - + + : 225 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
+ - - + +
+ + - +
- ]
9121 bruce@momjian.us 4023 : 8 : PG_RETURN_NULL();
4024 : :
4025 : 217 : ZERO_tmtc(&tmtc);
7616 tgl@sss.pgh.pa.us 4026 : 217 : tm = tmtcTm(&tmtc);
4027 : :
1608 4028 : 217 : interval2itm(*it, itm);
4029 : 217 : tmtc.fsec = itm->tm_usec;
4030 : 217 : tm->tm_sec = itm->tm_sec;
4031 : 217 : tm->tm_min = itm->tm_min;
4032 : 217 : tm->tm_hour = itm->tm_hour;
4033 : 217 : tm->tm_mday = itm->tm_mday;
4034 : 217 : tm->tm_mon = itm->tm_mon;
4035 : 217 : tm->tm_year = itm->tm_year;
4036 : :
4037 : : /* wday is meaningless, yday approximates the total span in days */
7616 4038 : 217 : tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
4039 : :
5679 peter_e@gmx.net 4040 [ - + ]: 217 : if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION())))
9121 bruce@momjian.us 4041 :UBC 0 : PG_RETURN_NULL();
4042 : :
9121 bruce@momjian.us 4043 :CBC 217 : PG_RETURN_TEXT_P(res);
4044 : : }
4045 : :
4046 : : /*
4047 : : * TO_TIMESTAMP()
4048 : : *
4049 : : * Make Timestamp from date_str which is formatted at argument 'fmt'
4050 : : * ( to_timestamp is reverse to_char() )
4051 : : */
4052 : : Datum
9553 4053 : 614 : to_timestamp(PG_FUNCTION_ARGS)
4054 : : {
3455 noah@leadboat.com 4055 : 614 : text *date_txt = PG_GETARG_TEXT_PP(0);
4056 : 614 : text *fmt = PG_GETARG_TEXT_PP(1);
2368 tgl@sss.pgh.pa.us 4057 : 614 : Oid collid = PG_GET_COLLATION();
4058 : : Timestamp result;
4059 : : int tz;
4060 : : struct pg_tm tm;
4061 : : struct fmt_tz ftz;
4062 : : fsec_t fsec;
4063 : : int fprec;
4064 : :
4065 : 614 : do_to_timestamp(date_txt, fmt, collid, false,
4066 : : &tm, &fsec, &ftz, &fprec, NULL, NULL);
4067 : :
4068 : : /* Use the specified time zone, if any. */
945 4069 [ + + ]: 502 : if (ftz.has_tz)
4070 : 64 : tz = ftz.gmtoffset;
4071 : : else
3152 andrew@dunslane.net 4072 : 438 : tz = DetermineTimeZoneOffset(&tm, session_timezone);
4073 : :
8403 tgl@sss.pgh.pa.us 4074 [ - + ]: 502 : if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
8403 tgl@sss.pgh.pa.us 4075 [ # # ]:UBC 0 : ereport(ERROR,
4076 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4077 : : errmsg("timestamp out of range")));
4078 : :
4079 : : /* Use the specified fractional precision, if any. */
2537 akorotkov@postgresql 4080 [ + + ]:CBC 502 : if (fprec)
1357 tgl@sss.pgh.pa.us 4081 : 168 : AdjustTimestampForTypmod(&result, fprec, NULL);
4082 : :
8403 4083 : 502 : PG_RETURN_TIMESTAMP(result);
4084 : : }
4085 : :
4086 : : /*
4087 : : * TO_DATE
4088 : : * Make Date from date_str which is formatted at argument 'fmt'
4089 : : */
4090 : : Datum
4091 : 135 : to_date(PG_FUNCTION_ARGS)
4092 : : {
3455 noah@leadboat.com 4093 : 135 : text *date_txt = PG_GETARG_TEXT_PP(0);
4094 : 135 : text *fmt = PG_GETARG_TEXT_PP(1);
2368 tgl@sss.pgh.pa.us 4095 : 135 : Oid collid = PG_GET_COLLATION();
4096 : : DateADT result;
4097 : : struct pg_tm tm;
4098 : : struct fmt_tz ftz;
4099 : : fsec_t fsec;
4100 : :
4101 : 135 : do_to_timestamp(date_txt, fmt, collid, false,
4102 : : &tm, &fsec, &ftz, NULL, NULL, NULL);
4103 : :
4104 : : /* Prevent overflow in Julian-day routines */
4973 4105 [ - + - - : 94 : if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
- - - + -
- - - ]
4973 tgl@sss.pgh.pa.us 4106 [ # # ]:UBC 0 : ereport(ERROR,
4107 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4108 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4109 : :
8403 tgl@sss.pgh.pa.us 4110 :CBC 94 : result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
4111 : :
4112 : : /* Now check for just-out-of-range dates */
3816 4113 [ + - - + ]: 94 : if (!IS_VALID_DATE(result))
3816 tgl@sss.pgh.pa.us 4114 [ # # ]:UBC 0 : ereport(ERROR,
4115 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4116 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4117 : :
8403 tgl@sss.pgh.pa.us 4118 :CBC 94 : PG_RETURN_DATEADT(result);
4119 : : }
4120 : :
4121 : : /*
4122 : : * Convert the 'date_txt' input to a datetime type using argument 'fmt'
4123 : : * as a format string. The collation 'collid' may be used for case-folding
4124 : : * rules in some cases. 'strict' specifies standard parsing mode.
4125 : : *
4126 : : * The actual data type (returned in 'typid', 'typmod') is determined by
4127 : : * the presence of date/time/zone components in the format string.
4128 : : *
4129 : : * When a timezone component is present, the corresponding offset is
4130 : : * returned in '*tz'.
4131 : : *
4132 : : * If escontext points to an ErrorSaveContext, data errors will be reported
4133 : : * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
4134 : : * whether an error occurred. Otherwise, errors are thrown.
4135 : : */
4136 : : Datum
2368 4137 : 26244 : parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
4138 : : Oid *typid, int32 *typmod, int *tz,
4139 : : Node *escontext)
4140 : : {
4141 : : struct pg_tm tm;
4142 : : struct fmt_tz ftz;
4143 : : fsec_t fsec;
4144 : : int fprec;
4145 : : uint32 flags;
4146 : :
1357 4147 [ + + ]: 26244 : if (!do_to_timestamp(date_txt, fmt, collid, strict,
4148 : : &tm, &fsec, &ftz, &fprec, &flags, escontext))
4149 : 20750 : return (Datum) 0;
4150 : :
2528 akorotkov@postgresql 4151 [ - + ]: 5454 : *typmod = fprec ? fprec : -1; /* fractional part precision */
4152 : :
4153 [ + + ]: 5454 : if (flags & DCH_DATED)
4154 : : {
4155 [ + + ]: 3364 : if (flags & DCH_TIMED)
4156 : : {
4157 [ + + ]: 2506 : if (flags & DCH_ZONED)
4158 : : {
4159 : : TimestampTz result;
4160 : :
945 tgl@sss.pgh.pa.us 4161 [ + - ]: 1437 : if (ftz.has_tz)
4162 : : {
4163 : 1437 : *tz = ftz.gmtoffset;
4164 : : }
4165 : : else
4166 : : {
4167 : : /*
4168 : : * Time zone is present in format string, but not in input
4169 : : * string. Assuming do_to_timestamp() triggers no error
4170 : : * this should be possible only in non-strict case.
4171 : : */
2528 akorotkov@postgresql 4172 [ # # ]:UBC 0 : Assert(!strict);
4173 : :
1357 tgl@sss.pgh.pa.us 4174 [ # # ]: 0 : ereturn(escontext, (Datum) 0,
4175 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4176 : : errmsg("missing time zone in input string for type timestamptz")));
4177 : : }
4178 : :
2528 akorotkov@postgresql 4179 [ - + ]:CBC 1437 : if (tm2timestamp(&tm, fsec, tz, &result) != 0)
1357 tgl@sss.pgh.pa.us 4180 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4181 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4182 : : errmsg("timestamptz out of range")));
4183 : :
1357 tgl@sss.pgh.pa.us 4184 :CBC 1437 : AdjustTimestampForTypmod(&result, *typmod, escontext);
4185 : :
2528 akorotkov@postgresql 4186 : 1437 : *typid = TIMESTAMPTZOID;
4187 : 1437 : return TimestampTzGetDatum(result);
4188 : : }
4189 : : else
4190 : : {
4191 : : Timestamp result;
4192 : :
4193 [ - + ]: 1069 : if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
1357 tgl@sss.pgh.pa.us 4194 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4195 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4196 : : errmsg("timestamp out of range")));
4197 : :
1357 tgl@sss.pgh.pa.us 4198 :CBC 1069 : AdjustTimestampForTypmod(&result, *typmod, escontext);
4199 : :
2528 akorotkov@postgresql 4200 : 1069 : *typid = TIMESTAMPOID;
4201 : 1069 : return TimestampGetDatum(result);
4202 : : }
4203 : : }
4204 : : else
4205 : : {
4206 [ - + ]: 858 : if (flags & DCH_ZONED)
4207 : : {
1357 tgl@sss.pgh.pa.us 4208 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4209 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4210 : : errmsg("datetime format is zoned but not timed")));
4211 : : }
4212 : : else
4213 : : {
4214 : : DateADT result;
4215 : :
4216 : : /* Prevent overflow in Julian-day routines */
2528 akorotkov@postgresql 4217 [ - + - - :CBC 858 : if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
- - - + -
- - - ]
1357 tgl@sss.pgh.pa.us 4218 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4219 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4220 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4221 : :
2528 akorotkov@postgresql 4222 :CBC 858 : result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
4223 : : POSTGRES_EPOCH_JDATE;
4224 : :
4225 : : /* Now check for just-out-of-range dates */
4226 [ + - - + ]: 858 : if (!IS_VALID_DATE(result))
1357 tgl@sss.pgh.pa.us 4227 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4228 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4229 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4230 : :
2528 akorotkov@postgresql 4231 :CBC 858 : *typid = DATEOID;
4232 : 858 : return DateADTGetDatum(result);
4233 : : }
4234 : : }
4235 : : }
4236 [ + - ]: 2090 : else if (flags & DCH_TIMED)
4237 : : {
4238 [ + + ]: 2090 : if (flags & DCH_ZONED)
4239 : : {
260 michael@paquier.xyz 4240 : 1181 : TimeTzADT *result = palloc_object(TimeTzADT);
4241 : :
945 tgl@sss.pgh.pa.us 4242 [ + - ]: 1181 : if (ftz.has_tz)
4243 : : {
4244 : 1181 : *tz = ftz.gmtoffset;
4245 : : }
4246 : : else
4247 : : {
4248 : : /*
4249 : : * Time zone is present in format string, but not in input
4250 : : * string. Assuming do_to_timestamp() triggers no error this
4251 : : * should be possible only in non-strict case.
4252 : : */
2528 akorotkov@postgresql 4253 [ # # ]:UBC 0 : Assert(!strict);
4254 : :
1357 tgl@sss.pgh.pa.us 4255 [ # # ]: 0 : ereturn(escontext, (Datum) 0,
4256 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4257 : : errmsg("missing time zone in input string for type timetz")));
4258 : : }
4259 : :
2528 akorotkov@postgresql 4260 [ - + ]:CBC 1181 : if (tm2timetz(&tm, fsec, *tz, result) != 0)
1357 tgl@sss.pgh.pa.us 4261 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4262 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4263 : : errmsg("timetz out of range")));
4264 : :
2528 akorotkov@postgresql 4265 :CBC 1181 : AdjustTimeForTypmod(&result->time, *typmod);
4266 : :
4267 : 1181 : *typid = TIMETZOID;
4268 : 1181 : return TimeTzADTPGetDatum(result);
4269 : : }
4270 : : else
4271 : : {
4272 : : TimeADT result;
4273 : :
4274 [ - + ]: 909 : if (tm2time(&tm, fsec, &result) != 0)
1357 tgl@sss.pgh.pa.us 4275 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4276 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4277 : : errmsg("time out of range")));
4278 : :
2528 akorotkov@postgresql 4279 :CBC 909 : AdjustTimeForTypmod(&result, *typmod);
4280 : :
4281 : 909 : *typid = TIMEOID;
4282 : 909 : return TimeADTGetDatum(result);
4283 : : }
4284 : : }
4285 : : else
4286 : : {
1357 tgl@sss.pgh.pa.us 4287 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4288 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4289 : : errmsg("datetime format is not dated and not timed")));
4290 : : }
4291 : : }
4292 : :
4293 : : /*
4294 : : * Parses the datetime format string in 'fmt_str' and returns true if it
4295 : : * contains a timezone specifier, false if not.
4296 : : */
4297 : : bool
889 amitlan@postgresql.o 4298 :CBC 44 : datetime_format_has_tz(const char *fmt_str)
4299 : : {
4300 : : bool incache;
302 peter@eisentraut.org 4301 : 44 : size_t fmt_len = strlen(fmt_str);
4302 : : int result;
4303 : : FormatNode *format;
4304 : :
889 amitlan@postgresql.o 4305 [ - + ]: 44 : if (fmt_len > DCH_CACHE_SIZE)
4306 : : {
4307 : : /*
4308 : : * Allocate new memory if format picture is bigger than static cache
4309 : : * and do not use cache (call parser always)
4310 : : */
889 amitlan@postgresql.o 4311 :UBC 0 : incache = false;
4312 : :
108 nathan@postgresql.or 4313 : 0 : format = palloc_array(FormatNode, fmt_len + 1);
4314 : :
889 amitlan@postgresql.o 4315 : 0 : parse_format(format, fmt_str, DCH_keywords,
4316 : : DCH_suff, DCH_index, DCH_FLAG, NULL);
4317 : : }
4318 : : else
4319 : : {
4320 : : /*
4321 : : * Use cache buffers
4322 : : */
889 amitlan@postgresql.o 4323 :CBC 44 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
4324 : :
4325 : 44 : incache = true;
4326 : 44 : format = ent->format;
4327 : : }
4328 : :
4329 : 44 : result = DCH_datetime_type(format);
4330 : :
4331 [ - + ]: 44 : if (!incache)
889 amitlan@postgresql.o 4332 :UBC 0 : pfree(format);
4333 : :
889 amitlan@postgresql.o 4334 :CBC 44 : return result & DCH_ZONED;
4335 : : }
4336 : :
4337 : : /*
4338 : : * do_to_timestamp: shared code for to_timestamp and to_date
4339 : : *
4340 : : * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
4341 : : * fractional seconds, struct fmt_tz, and fractional precision.
4342 : : *
4343 : : * 'collid' identifies the collation to use, if needed.
4344 : : * 'std' specifies standard parsing mode.
4345 : : *
4346 : : * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags',
4347 : : * if that is not NULL.
4348 : : *
4349 : : * Returns true on success, false on failure (if escontext points to an
4350 : : * ErrorSaveContext; otherwise errors are thrown). Note that currently,
4351 : : * soft-error behavior is provided for bad data but not bad format.
4352 : : *
4353 : : * We parse 'fmt' into a list of FormatNodes, which is then passed to
4354 : : * DCH_from_char to populate a TmFromChar with the parsed contents of
4355 : : * 'date_txt'.
4356 : : *
4357 : : * The TmFromChar is then analysed and converted into the final results in
4358 : : * struct 'tm', 'fsec', struct 'tz', and 'fprec'.
4359 : : */
4360 : : static bool
302 peter@eisentraut.org 4361 : 26993 : do_to_timestamp(const text *date_txt, const text *fmt, Oid collid, bool std,
4362 : : struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
4363 : : int *fprec, uint32 *flags, Node *escontext)
4364 : : {
2528 akorotkov@postgresql 4365 : 26993 : FormatNode *format = NULL;
301 peter@eisentraut.org 4366 : 26993 : TmFromChar tmfc = {0};
4367 : : int fmt_len;
4368 : : char *date_str;
4369 : : int fmask;
2528 akorotkov@postgresql 4370 : 26993 : bool incache = false;
4371 : :
2451 michael@paquier.xyz 4372 [ - + ]: 26993 : Assert(tm != NULL);
4373 [ - + ]: 26993 : Assert(fsec != NULL);
4374 : :
3620 tgl@sss.pgh.pa.us 4375 : 26993 : date_str = text_to_cstring(date_txt);
4376 : :
8403 4377 : 26993 : ZERO_tm(tm);
4378 : 26993 : *fsec = 0;
945 4379 : 26993 : tz->has_tz = false;
2451 michael@paquier.xyz 4380 [ + + ]: 26993 : if (fprec)
4381 : 26858 : *fprec = 0;
4382 [ + + ]: 26993 : if (flags)
4383 : 26244 : *flags = 0;
3620 tgl@sss.pgh.pa.us 4384 : 26993 : fmask = 0; /* bit mask for ValidateDate() */
4385 : :
6729 4386 : 26993 : fmt_len = VARSIZE_ANY_EXHDR(fmt);
4387 : :
8394 4388 [ + - ]: 26993 : if (fmt_len)
4389 : : {
4390 : : char *fmt_str;
4391 : :
6729 4392 : 26993 : fmt_str = text_to_cstring(fmt);
4393 : :
8394 4394 [ - + ]: 26993 : if (fmt_len > DCH_CACHE_SIZE)
4395 : : {
4396 : : /*
4397 : : * Allocate new memory if format picture is bigger than static
4398 : : * cache and do not use cache (call parser always)
4399 : : */
108 nathan@postgresql.or 4400 :UBC 0 : format = palloc_array(FormatNode, fmt_len + 1);
4401 : :
2528 akorotkov@postgresql 4402 [ # # ]: 0 : parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
4403 : : DCH_FLAG | (std ? STD_FLAG : 0), NULL);
4404 : : }
4405 : : else
4406 : : {
4407 : : /*
4408 : : * Use cache buffers
4409 : : */
2528 akorotkov@postgresql 4410 :CBC 26993 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
4411 : :
3298 peter_e@gmx.net 4412 : 26989 : incache = true;
9633 bruce@momjian.us 4413 : 26989 : format = ent->format;
4414 : : }
4415 : :
4416 : : #ifdef DEBUG_TO_FROM_CHAR
4417 : : /* dump_node(format, fmt_len); */
4418 : : /* dump_index(DCH_keywords, DCH_index); */
4419 : : #endif
4420 : :
1357 tgl@sss.pgh.pa.us 4421 : 26989 : DCH_from_char(format, date_str, &tmfc, collid, std, escontext);
8394 4422 : 26888 : pfree(fmt_str);
1357 4423 [ + + + - : 26888 : if (SOFT_ERROR_OCCURRED(escontext))
+ + ]
4424 : 20750 : goto fail;
4425 : :
2528 akorotkov@postgresql 4426 [ + + ]: 6138 : if (flags)
1357 tgl@sss.pgh.pa.us 4427 : 5454 : *flags = DCH_datetime_type(format);
4428 : :
8394 4429 [ - + ]: 6138 : if (!incache)
4430 : : {
9711 bruce@momjian.us 4431 :UBC 0 : pfree(format);
2528 akorotkov@postgresql 4432 : 0 : format = NULL;
4433 : : }
4434 : : }
4435 : :
4436 : : DEBUG_TMFC(&tmfc);
4437 : :
4438 : : /*
4439 : : * Convert to_date/to_timestamp input fields to standard 'tm'
4440 : : */
9121 bruce@momjian.us 4441 [ + + ]:CBC 6138 : if (tmfc.ssss)
4442 : : {
9072 4443 : 16 : int x = tmfc.ssss;
4444 : :
7707 4445 : 16 : tm->tm_hour = x / SECS_PER_HOUR;
4446 : 16 : x %= SECS_PER_HOUR;
4447 : 16 : tm->tm_min = x / SECS_PER_MINUTE;
4448 : 16 : x %= SECS_PER_MINUTE;
8403 tgl@sss.pgh.pa.us 4449 : 16 : tm->tm_sec = x;
4450 : : }
4451 : :
9121 bruce@momjian.us 4452 [ + + ]: 6138 : if (tmfc.ss)
8403 tgl@sss.pgh.pa.us 4453 : 908 : tm->tm_sec = tmfc.ss;
9121 bruce@momjian.us 4454 [ + + ]: 6138 : if (tmfc.mi)
8403 tgl@sss.pgh.pa.us 4455 : 4852 : tm->tm_min = tmfc.mi;
9121 bruce@momjian.us 4456 [ + + ]: 6138 : if (tmfc.hh)
8403 tgl@sss.pgh.pa.us 4457 : 4896 : tm->tm_hour = tmfc.hh;
4458 : :
300 peter@eisentraut.org 4459 [ + + ]: 6138 : if (tmfc.clock_12_hour)
4460 : : {
5647 bruce@momjian.us 4461 [ + - + + ]: 80 : if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
4462 : : {
1357 tgl@sss.pgh.pa.us 4463 [ + - ]: 4 : errsave(escontext,
4464 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4465 : : errmsg("hour \"%d\" is invalid for the 12-hour clock", tm->tm_hour),
4466 : : errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
1357 tgl@sss.pgh.pa.us 4467 :UBC 0 : goto fail;
4468 : : }
4469 : :
5647 bruce@momjian.us 4470 [ + + + - ]:CBC 76 : if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
4471 : 8 : tm->tm_hour += HOURS_PER_DAY / 2;
4472 [ + - - + ]: 68 : else if (!tmfc.pm && tm->tm_hour == HOURS_PER_DAY / 2)
8403 tgl@sss.pgh.pa.us 4473 :UBC 0 : tm->tm_hour = 0;
4474 : : }
4475 : :
6559 tgl@sss.pgh.pa.us 4476 [ + + ]:CBC 6134 : if (tmfc.year)
4477 : : {
4478 : : /*
4479 : : * If CC and YY (or Y) are provided, use YY as 2 low-order digits for
4480 : : * the year in the given century. Keep in mind that the 21st century
4481 : : * AD runs from 2001-2100, not 2000-2099; 6th century BC runs from
4482 : : * 600BC to 501BC.
4483 : : */
7167 4484 [ + + + - ]: 4020 : if (tmfc.cc && tmfc.yysz <= 2)
4485 : : {
5133 bruce@momjian.us 4486 [ - + ]: 12 : if (tmfc.bc)
5133 bruce@momjian.us 4487 :UBC 0 : tmfc.cc = -tmfc.cc;
6559 tgl@sss.pgh.pa.us 4488 :CBC 12 : tm->tm_year = tmfc.year % 100;
7167 4489 [ + - ]: 12 : if (tm->tm_year)
4490 : : {
4491 : : int tmp;
4492 : :
5133 bruce@momjian.us 4493 [ + + ]: 12 : if (tmfc.cc >= 0)
4494 : : {
4495 : : /* tm->tm_year += (tmfc.cc - 1) * 100; */
626 nathan@postgresql.or 4496 : 8 : tmp = tmfc.cc - 1;
4497 [ + + - + ]: 12 : if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
4498 : 4 : pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year))
4499 : : {
4500 : 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4501 : 4 : text_to_cstring(date_txt), "timestamp",
4502 : : escontext);
626 nathan@postgresql.or 4503 :UBC 0 : goto fail;
4504 : : }
4505 : : }
4506 : : else
4507 : : {
4508 : : /* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */
626 nathan@postgresql.or 4509 :CBC 4 : tmp = tmfc.cc + 1;
4510 [ - + - - ]: 4 : if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
626 nathan@postgresql.or 4511 [ # # ]:UBC 0 : pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) ||
4512 : 0 : pg_add_s32_overflow(tmp, 1, &tm->tm_year))
4513 : : {
626 nathan@postgresql.or 4514 :CBC 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4515 : 4 : text_to_cstring(date_txt), "timestamp",
4516 : : escontext);
626 nathan@postgresql.or 4517 :UBC 0 : goto fail;
4518 : : }
4519 : : }
4520 : : }
4521 : : else
4522 : : {
4523 : : /* find century year for dates ending in "00" */
4838 bruce@momjian.us 4524 : 0 : tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
4525 : : }
4526 : : }
4527 : : else
4528 : : {
4529 : : /* If a 4-digit year is provided, we use that and ignore CC. */
6559 tgl@sss.pgh.pa.us 4530 :CBC 4008 : tm->tm_year = tmfc.year;
2157 4531 [ + + ]: 4008 : if (tmfc.bc)
4532 : 24 : tm->tm_year = -tm->tm_year;
4533 : : /* correct for our representation of BC years */
4534 [ + + ]: 4008 : if (tm->tm_year < 0)
4535 : 24 : tm->tm_year++;
4536 : : }
3620 4537 : 4012 : fmask |= DTK_M(YEAR);
4538 : : }
4539 [ + + ]: 2114 : else if (tmfc.cc)
4540 : : {
4541 : : /* use first year of century */
5133 bruce@momjian.us 4542 [ - + ]: 8 : if (tmfc.bc)
5133 bruce@momjian.us 4543 :UBC 0 : tmfc.cc = -tmfc.cc;
5133 bruce@momjian.us 4544 [ + + ]:CBC 8 : if (tmfc.cc >= 0)
4545 : : {
4546 : : /* +1 because 21st century started in 2001 */
4547 : : /* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */
626 nathan@postgresql.or 4548 [ - + - - ]: 4 : if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) ||
626 nathan@postgresql.or 4549 :UBC 0 : pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4550 : : {
626 nathan@postgresql.or 4551 :CBC 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4552 : 4 : text_to_cstring(date_txt), "timestamp",
4553 : : escontext);
626 nathan@postgresql.or 4554 :UBC 0 : goto fail;
4555 : : }
4556 : : }
4557 : : else
4558 : : {
4559 : : /* +1 because year == 599 is 600 BC */
4560 : : /* tm->tm_year = tmfc.cc * 100 + 1; */
626 nathan@postgresql.or 4561 [ - + - - ]:CBC 4 : if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
626 nathan@postgresql.or 4562 :UBC 0 : pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4563 : : {
626 nathan@postgresql.or 4564 :CBC 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4565 : 4 : text_to_cstring(date_txt), "timestamp",
4566 : : escontext);
626 nathan@postgresql.or 4567 :UBC 0 : goto fail;
4568 : : }
4569 : : }
3620 tgl@sss.pgh.pa.us 4570 : 0 : fmask |= DTK_M(YEAR);
4571 : : }
4572 : :
9121 bruce@momjian.us 4573 [ + + ]:CBC 6118 : if (tmfc.j)
4574 : : {
8403 tgl@sss.pgh.pa.us 4575 : 4 : j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3620 4576 : 4 : fmask |= DTK_DATE_M;
4577 : : }
4578 : :
6559 4579 [ + + ]: 6118 : if (tmfc.ww)
4580 : : {
4581 [ + + ]: 24 : if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
4582 : : {
4583 : : /*
4584 : : * If tmfc.d is not set, then the date is left at the beginning of
4585 : : * the ISO week (Monday).
4586 : : */
4587 [ + - ]: 16 : if (tmfc.d)
4588 : 16 : isoweekdate2date(tmfc.ww, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
4589 : : else
6559 tgl@sss.pgh.pa.us 4590 :UBC 0 : isoweek2date(tmfc.ww, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3620 tgl@sss.pgh.pa.us 4591 :CBC 16 : fmask |= DTK_DATE_M;
4592 : : }
4593 : : else
4594 : : {
4595 : : /* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */
626 nathan@postgresql.or 4596 [ + - + + ]: 16 : if (pg_sub_s32_overflow(tmfc.ww, 1, &tmfc.ddd) ||
4597 [ - + ]: 12 : pg_mul_s32_overflow(tmfc.ddd, 7, &tmfc.ddd) ||
4598 : 4 : pg_add_s32_overflow(tmfc.ddd, 1, &tmfc.ddd))
4599 : : {
4600 : 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4601 : : date_str, "timestamp", escontext);
626 nathan@postgresql.or 4602 :UBC 0 : goto fail;
4603 : : }
4604 : : }
4605 : : }
4606 : :
6559 tgl@sss.pgh.pa.us 4607 [ + + ]:CBC 6114 : if (tmfc.w)
4608 : : {
4609 : : /* tmfc.dd = (tmfc.w - 1) * 7 + 1; */
626 nathan@postgresql.or 4610 [ + - + + ]: 16 : if (pg_sub_s32_overflow(tmfc.w, 1, &tmfc.dd) ||
4611 [ - + ]: 12 : pg_mul_s32_overflow(tmfc.dd, 7, &tmfc.dd) ||
4612 : 4 : pg_add_s32_overflow(tmfc.dd, 1, &tmfc.dd))
4613 : : {
4614 : 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4615 : : date_str, "timestamp", escontext);
626 nathan@postgresql.or 4616 :UBC 0 : goto fail;
4617 : : }
4618 : : }
9121 bruce@momjian.us 4619 [ + + ]:CBC 6110 : if (tmfc.dd)
4620 : : {
8403 tgl@sss.pgh.pa.us 4621 : 3920 : tm->tm_mday = tmfc.dd;
3620 4622 : 3920 : fmask |= DTK_M(DAY);
4623 : : }
9121 bruce@momjian.us 4624 [ + + ]: 6110 : if (tmfc.mm)
4625 : : {
8403 tgl@sss.pgh.pa.us 4626 : 3944 : tm->tm_mon = tmfc.mm;
3620 4627 : 3944 : fmask |= DTK_M(MONTH);
4628 : : }
4629 : :
8403 4630 [ + + - + : 6110 : if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1))
- - ]
4631 : : {
4632 : : /*
4633 : : * The month and day field have not been set, so we use the
4634 : : * day-of-year field to populate them. Depending on the date mode,
4635 : : * this field may be interpreted as a Gregorian day-of-year, or an ISO
4636 : : * week date day-of-year.
4637 : : */
4638 : :
6374 4639 [ - + - - ]: 32 : if (!tm->tm_year && !tmfc.bc)
4640 : : {
1357 tgl@sss.pgh.pa.us 4641 [ # # ]:UBC 0 : errsave(escontext,
4642 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4643 : : errmsg("cannot calculate day of year without year information")));
4644 : 0 : goto fail;
4645 : : }
4646 : :
6559 tgl@sss.pgh.pa.us 4647 [ + + ]:CBC 32 : if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
4648 : : {
4649 : : int j0; /* zeroth day of the ISO year, in Julian */
4650 : :
6374 4651 : 4 : j0 = isoweek2j(tm->tm_year, 1) - 1;
4652 : :
7132 bruce@momjian.us 4653 : 4 : j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3620 tgl@sss.pgh.pa.us 4654 : 4 : fmask |= DTK_DATE_M;
4655 : : }
4656 : : else
4657 : : {
4658 : : const int *y;
4659 : : int i;
4660 : :
4661 : : static const int ysum[2][13] = {
4662 : : {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
4663 : : {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
4664 : :
7132 bruce@momjian.us 4665 [ + + - + : 28 : y = ysum[isleap(tm->tm_year)];
- - ]
4666 : :
5647 4667 [ + + ]: 328 : for (i = 1; i <= MONTHS_PER_YEAR; i++)
4668 : : {
3620 tgl@sss.pgh.pa.us 4669 [ + + ]: 320 : if (tmfc.ddd <= y[i])
7132 bruce@momjian.us 4670 : 20 : break;
4671 : : }
4672 [ + - ]: 28 : if (tm->tm_mon <= 1)
6374 tgl@sss.pgh.pa.us 4673 : 28 : tm->tm_mon = i;
4674 : :
7132 bruce@momjian.us 4675 [ + - ]: 28 : if (tm->tm_mday <= 1)
6374 tgl@sss.pgh.pa.us 4676 : 28 : tm->tm_mday = tmfc.ddd - y[i - 1];
4677 : :
3620 4678 : 28 : fmask |= DTK_M(MONTH) | DTK_M(DAY);
4679 : : }
4680 : : }
4681 : :
8894 lockhart@fourpalms.o 4682 [ + + ]: 6110 : if (tmfc.ms)
4683 : : {
626 nathan@postgresql.or 4684 : 8 : int tmp = 0;
4685 : :
4686 : : /* *fsec += tmfc.ms * 1000; */
4687 [ + + - + ]: 12 : if (pg_mul_s32_overflow(tmfc.ms, 1000, &tmp) ||
4688 : 4 : pg_add_s32_overflow(*fsec, tmp, fsec))
4689 : : {
4690 : 4 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4691 : : date_str, "timestamp", escontext);
626 nathan@postgresql.or 4692 :UBC 0 : goto fail;
4693 : : }
4694 : : }
8894 lockhart@fourpalms.o 4695 [ + + ]:CBC 6106 : if (tmfc.us)
8403 tgl@sss.pgh.pa.us 4696 : 676 : *fsec += tmfc.us;
2537 akorotkov@postgresql 4697 [ + + ]: 6106 : if (fprec)
4698 : 5992 : *fprec = tmfc.ff; /* fractional precision, if specified */
4699 : :
4700 : : /* Range-check date fields according to bit mask computed above */
3620 tgl@sss.pgh.pa.us 4701 [ + + ]: 6106 : if (fmask != 0)
4702 : : {
4703 : : /* We already dealt with AD/BC, so pass isjulian = true */
4704 : 4016 : int dterr = ValidateDate(fmask, true, false, false, tm);
4705 : :
4706 [ + + ]: 4016 : if (dterr != 0)
4707 : : {
4708 : : /*
4709 : : * Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate
4710 : : * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
4711 : : * irrelevant hint about datestyle.
4712 : : */
1357 4713 : 32 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4714 : : date_str, "timestamp", escontext);
1357 tgl@sss.pgh.pa.us 4715 :UBC 0 : goto fail;
4716 : : }
4717 : : }
4718 : :
4719 : : /* Range-check time fields too */
3620 tgl@sss.pgh.pa.us 4720 [ + - + + ]:CBC 6074 : if (tm->tm_hour < 0 || tm->tm_hour >= HOURS_PER_DAY ||
4721 [ + - + + ]: 6062 : tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
4722 [ + - + + ]: 6058 : tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
3472 4723 [ + - + + ]: 6054 : *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
4724 : : {
1357 4725 : 24 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4726 : : date_str, "timestamp", escontext);
1357 tgl@sss.pgh.pa.us 4727 :UBC 0 : goto fail;
4728 : : }
4729 : :
4730 : : /*
4731 : : * If timezone info was present, reduce it to a GMT offset. (We cannot do
4732 : : * this until we've filled all of the tm struct, since the zone's offset
4733 : : * might be time-varying.)
4734 : : */
3152 andrew@dunslane.net 4735 [ + + ]:CBC 6050 : if (tmfc.tzsign)
4736 : : {
4737 : : /* TZH and/or TZM fields */
4738 [ + - + - ]: 2658 : if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
4739 [ + - - + ]: 2658 : tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
4740 : : {
1357 tgl@sss.pgh.pa.us 4741 :UBC 0 : DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL,
4742 : : date_str, "timestamp", escontext);
4743 : 0 : goto fail;
4744 : : }
4745 : :
945 tgl@sss.pgh.pa.us 4746 :CBC 2658 : tz->has_tz = true;
4747 : 2658 : tz->gmtoffset = (tmfc.tzh * MINS_PER_HOUR + tmfc.tzm) * SECS_PER_MINUTE;
4748 : : /* note we are flipping the sign convention here */
4749 [ + + ]: 2658 : if (tmfc.tzsign > 0)
4750 : 2426 : tz->gmtoffset = -tz->gmtoffset;
4751 : : }
4752 [ + + ]: 3392 : else if (tmfc.has_tz)
4753 : : {
4754 : : /* TZ field */
4755 : 24 : tz->has_tz = true;
4756 [ + + ]: 24 : if (tmfc.tzp == NULL)
4757 : : {
4758 : : /* fixed-offset abbreviation; flip the sign convention */
4759 : 20 : tz->gmtoffset = -tmfc.gmtoffset;
4760 : : }
4761 : : else
4762 : : {
4763 : : /* dynamic-offset abbreviation, resolve using specified time */
4764 : 4 : tz->gmtoffset = DetermineTimeZoneAbbrevOffset(tm, tmfc.abbrev,
4765 : : tmfc.tzp);
4766 : : }
4767 : : }
4768 : :
4769 : : DEBUG_TM(tm);
4770 : :
2528 akorotkov@postgresql 4771 [ + - - + ]: 6050 : if (format && !incache)
2528 akorotkov@postgresql 4772 :UBC 0 : pfree(format);
1357 tgl@sss.pgh.pa.us 4773 :CBC 6050 : pfree(date_str);
4774 : :
4775 : 6050 : return true;
4776 : :
4777 : 20750 : fail:
4778 [ + - - + ]: 20750 : if (format && !incache)
1357 tgl@sss.pgh.pa.us 4779 :UBC 0 : pfree(format);
3620 tgl@sss.pgh.pa.us 4780 :CBC 20750 : pfree(date_str);
4781 : :
1357 4782 : 20750 : return false;
4783 : : }
4784 : :
4785 : :
4786 : : /**********************************************************************
4787 : : * the NUMBER version part
4788 : : *********************************************************************/
4789 : :
4790 : :
4791 : : /*
4792 : : * Fill str with character c max times, and add terminating \0. (So max+1
4793 : : * bytes are written altogether!)
4794 : : */
4795 : : static void
9711 bruce@momjian.us 4796 : 152 : fill_str(char *str, int c, int max)
4797 : : {
4798 : 152 : memset(str, c, max);
302 peter@eisentraut.org 4799 : 152 : str[max] = '\0';
9711 bruce@momjian.us 4800 : 152 : }
4801 : :
4802 : : /* This works the same as DCH_prevent_counter_overflow */
4803 : : static inline void
2872 tgl@sss.pgh.pa.us 4804 : 699753 : NUM_prevent_counter_overflow(void)
4805 : : {
4806 [ - + ]: 699753 : if (NUMCounter >= (INT_MAX - 1))
4807 : : {
2872 tgl@sss.pgh.pa.us 4808 [ # # ]:UBC 0 : for (int i = 0; i < n_NUMCache; i++)
4809 : 0 : NUMCache[i]->age >>= 1;
4810 : 0 : NUMCounter >>= 1;
4811 : : }
2872 tgl@sss.pgh.pa.us 4812 :CBC 699753 : }
4813 : :
4814 : : /* select a NUMCacheEntry to hold the given format picture */
4815 : : static NUMCacheEntry *
3620 4816 : 409 : NUM_cache_getnew(const char *str)
4817 : : {
4818 : : NUMCacheEntry *ent;
4819 : :
4820 : : /* Ensure we can advance NUMCounter below */
2872 4821 : 409 : NUM_prevent_counter_overflow();
4822 : :
4823 : : /*
4824 : : * If cache is full, remove oldest entry (or recycle first not-valid one)
4825 : : */
3620 4826 [ + + ]: 409 : if (n_NUMCache >= NUM_CACHE_ENTRIES)
4827 : : {
2872 4828 : 196 : NUMCacheEntry *old = NUMCache[0];
4829 : :
4830 : : #ifdef DEBUG_TO_FROM_CHAR
4831 : : elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
4832 : : #endif
3620 4833 [ + - ]: 196 : if (old->valid)
4834 : : {
2872 4835 [ + + ]: 3852 : for (int i = 1; i < NUM_CACHE_ENTRIES; i++)
4836 : : {
4837 : 3660 : ent = NUMCache[i];
3620 4838 [ + + ]: 3660 : if (!ent->valid)
4839 : : {
4840 : 4 : old = ent;
4841 : 4 : break;
4842 : : }
4843 [ + + ]: 3656 : if (ent->age < old->age)
4844 : 196 : old = ent;
4845 : : }
4846 : : }
4847 : : #ifdef DEBUG_TO_FROM_CHAR
4848 : : elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age);
4849 : : #endif
4850 : 196 : old->valid = false;
2208 peter@eisentraut.org 4851 : 196 : strlcpy(old->str, str, NUM_CACHE_SIZE + 1);
9660 bruce@momjian.us 4852 : 196 : old->age = (++NUMCounter);
4853 : : /* caller is expected to fill format and Num, then set valid */
3620 tgl@sss.pgh.pa.us 4854 : 196 : return old;
4855 : : }
4856 : : else
4857 : : {
4858 : : #ifdef DEBUG_TO_FROM_CHAR
4859 : : elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
4860 : : #endif
2872 4861 [ - + ]: 213 : Assert(NUMCache[n_NUMCache] == NULL);
4862 : 213 : NUMCache[n_NUMCache] = ent = (NUMCacheEntry *)
4863 : 213 : MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry));
3620 4864 : 213 : ent->valid = false;
2208 peter@eisentraut.org 4865 : 213 : strlcpy(ent->str, str, NUM_CACHE_SIZE + 1);
9660 bruce@momjian.us 4866 : 213 : ent->age = (++NUMCounter);
4867 : : /* caller is expected to fill format and Num, then set valid */
4868 : 213 : ++n_NUMCache;
3620 tgl@sss.pgh.pa.us 4869 : 213 : return ent;
4870 : : }
4871 : : }
4872 : :
4873 : : /* look for an existing NUMCacheEntry matching the given format picture */
4874 : : static NUMCacheEntry *
4875 : 699344 : NUM_cache_search(const char *str)
4876 : : {
4877 : : /* Ensure we can advance NUMCounter below */
2872 4878 : 699344 : NUM_prevent_counter_overflow();
4879 : :
4880 [ + + ]: 878910 : for (int i = 0; i < n_NUMCache; i++)
4881 : : {
4882 : 878501 : NUMCacheEntry *ent = NUMCache[i];
4883 : :
3620 4884 [ + + + + ]: 878501 : if (ent->valid && strcmp(ent->str, str) == 0)
4885 : : {
9660 bruce@momjian.us 4886 : 698935 : ent->age = (++NUMCounter);
4887 : 698935 : return ent;
4888 : : }
4889 : : }
4890 : :
8268 neilc@samurai.com 4891 : 409 : return NULL;
4892 : : }
4893 : :
4894 : : /* Find or create a NUMCacheEntry for the given format picture */
4895 : : static NUMCacheEntry *
3620 tgl@sss.pgh.pa.us 4896 : 699344 : NUM_cache_fetch(const char *str)
4897 : : {
4898 : : NUMCacheEntry *ent;
4899 : :
4900 [ + + ]: 699344 : if ((ent = NUM_cache_search(str)) == NULL)
4901 : : {
4902 : : /*
4903 : : * Not in the cache, must run parser and save a new format-picture to
4904 : : * the cache. Do not mark the cache entry valid until parsing
4905 : : * succeeds.
4906 : : */
4907 : 409 : ent = NUM_cache_getnew(str);
4908 : :
301 peter@eisentraut.org 4909 : 409 : memset(&ent->Num, 0, sizeof ent->Num);
4910 : :
3620 tgl@sss.pgh.pa.us 4911 : 409 : parse_format(ent->format, str, NUM_keywords,
4912 : : NULL, NUM_index, NUM_FLAG, &ent->Num);
4913 : :
4914 : 401 : ent->valid = true;
4915 : : }
4916 : 699336 : return ent;
4917 : : }
4918 : :
4919 : : /*
4920 : : * Cache routine for NUM to_char version
4921 : : */
4922 : : static FormatNode *
302 peter@eisentraut.org 4923 : 699544 : NUM_cache(int len, NUMDesc *Num, const text *pars_str, bool *shouldFree)
4924 : : {
9633 bruce@momjian.us 4925 : 699544 : FormatNode *format = NULL;
4926 : : char *str;
4927 : :
6729 tgl@sss.pgh.pa.us 4928 : 699544 : str = text_to_cstring(pars_str);
4929 : :
9633 bruce@momjian.us 4930 [ + + ]: 699544 : if (len > NUM_CACHE_SIZE)
4931 : : {
4932 : : /*
4933 : : * Allocate new memory if format picture is bigger than static cache
4934 : : * and do not use cache (call parser always)
4935 : : */
108 nathan@postgresql.or 4936 : 200 : format = palloc_array(FormatNode, len + 1);
4937 : :
8742 bruce@momjian.us 4938 : 200 : *shouldFree = true;
4939 : :
301 peter@eisentraut.org 4940 : 200 : memset(Num, 0, sizeof *Num);
4941 : :
9633 bruce@momjian.us 4942 : 200 : parse_format(format, str, NUM_keywords,
4943 : : NULL, NUM_index, NUM_FLAG, Num);
4944 : : }
4945 : : else
4946 : : {
4947 : : /*
4948 : : * Use cache buffers
4949 : : */
3620 tgl@sss.pgh.pa.us 4950 : 699344 : NUMCacheEntry *ent = NUM_cache_fetch(str);
4951 : :
8742 bruce@momjian.us 4952 : 699336 : *shouldFree = false;
4953 : :
9633 4954 : 699336 : format = ent->format;
4955 : :
4956 : : /*
4957 : : * Copy cache to used struct
4958 : : */
4367 4959 : 699336 : Num->flag = ent->Num.flag;
4960 : 699336 : Num->lsign = ent->Num.lsign;
4961 : 699336 : Num->pre = ent->Num.pre;
4962 : 699336 : Num->post = ent->Num.post;
4963 : 699336 : Num->pre_lsign_num = ent->Num.pre_lsign_num;
4964 : 699336 : Num->need_locale = ent->Num.need_locale;
4965 : 699336 : Num->multi = ent->Num.multi;
4966 : 699336 : Num->zero_start = ent->Num.zero_start;
4967 : 699336 : Num->zero_end = ent->Num.zero_end;
4968 : : }
4969 : :
4970 : : #ifdef DEBUG_TO_FROM_CHAR
4971 : : /* dump_node(format, len); */
4972 : : dump_index(NUM_keywords, NUM_index);
4973 : : #endif
4974 : :
9660 4975 : 699536 : pfree(str);
4976 : 699536 : return format;
4977 : : }
4978 : :
4979 : :
4980 : : /*
4981 : : * Convert integer to Roman numerals
4982 : : * Result is upper-case and not blank-padded (NUM_processor converts as needed)
4983 : : * If input is out-of-range, produce '###############'
4984 : : */
4985 : : static char *
9711 4986 : 16088 : int_to_roman(int number)
4987 : : {
4988 : : int len,
4989 : : num;
4990 : : char *result,
4991 : : numstr[12];
4992 : :
582 tgl@sss.pgh.pa.us 4993 : 16088 : result = (char *) palloc(MAX_ROMAN_LEN + 1);
9711 bruce@momjian.us 4994 : 16088 : *result = '\0';
4995 : :
4996 : : /*
4997 : : * This range limit is the same as in Oracle(TM). The difficulty with
4998 : : * handling 4000 or more is that we'd need to use more than 3 "M"'s, and
4999 : : * more than 3 of the same digit isn't considered a valid Roman string.
5000 : : */
9633 5001 [ + + + + ]: 16088 : if (number > 3999 || number < 1)
5002 : : {
582 tgl@sss.pgh.pa.us 5003 : 60 : fill_str(result, '#', MAX_ROMAN_LEN);
9711 bruce@momjian.us 5004 : 60 : return result;
5005 : : }
5006 : :
5007 : : /* Convert to decimal, then examine each digit */
9312 ishii@postgresql.org 5008 : 16028 : len = snprintf(numstr, sizeof(numstr), "%d", number);
700 tgl@sss.pgh.pa.us 5009 [ + - - + ]: 16028 : Assert(len > 0 && len <= 4);
5010 : :
303 peter@eisentraut.org 5011 [ + + ]: 75688 : for (char *p = numstr; *p != '\0'; p++, --len)
5012 : : {
2182 tgl@sss.pgh.pa.us 5013 : 59660 : num = *p - ('0' + 1);
9711 bruce@momjian.us 5014 [ + + ]: 59660 : if (num < 0)
700 tgl@sss.pgh.pa.us 5015 : 4364 : continue; /* ignore zeroes */
5016 : : /* switch on current column position */
5017 [ + + + + : 55296 : switch (len)
- ]
5018 : : {
5019 : 12016 : case 4:
5020 [ + + ]: 36032 : while (num-- >= 0)
5021 : 24016 : strcat(result, "M");
5022 : 12016 : break;
5023 : 14428 : case 3:
9633 bruce@momjian.us 5024 : 14428 : strcat(result, rm100[num]);
700 tgl@sss.pgh.pa.us 5025 : 14428 : break;
5026 : 14424 : case 2:
9711 bruce@momjian.us 5027 : 14424 : strcat(result, rm10[num]);
700 tgl@sss.pgh.pa.us 5028 : 14424 : break;
5029 : 14428 : case 1:
9711 bruce@momjian.us 5030 : 14428 : strcat(result, rm1[num]);
700 tgl@sss.pgh.pa.us 5031 : 14428 : break;
5032 : : }
5033 : : }
9711 bruce@momjian.us 5034 : 16028 : return result;
5035 : : }
5036 : :
5037 : : /*
5038 : : * Convert a roman numeral (standard form) to an integer.
5039 : : * Result is an integer between 1 and 3999.
5040 : : * Np->input_p is advanced past the characters consumed.
5041 : : *
5042 : : * If input is invalid, return -1.
5043 : : */
5044 : : static int
7 tgl@sss.pgh.pa.us 5045 :GNC 16072 : roman_to_int(NUMProc *Np)
5046 : : {
582 tgl@sss.pgh.pa.us 5047 :CBC 16072 : int result = 0;
5048 : : size_t len;
5049 : : char romanChars[MAX_ROMAN_LEN];
5050 : : int romanValues[MAX_ROMAN_LEN];
5051 : 16072 : int repeatCount = 1;
5052 : 16072 : int vCount = 0,
5053 : 16072 : lCount = 0,
5054 : 16072 : dCount = 0;
5055 : 16072 : bool subtractionEncountered = false;
5056 : 16072 : int lastSubtractedValue = 0;
5057 : :
5058 : : /*
5059 : : * Skip any leading whitespace. Perhaps we should limit the amount of
5060 : : * space skipped to MAX_ROMAN_LEN, but that seems unnecessarily picky.
5061 : : */
7 tgl@sss.pgh.pa.us 5062 [ + + + + ]:GNC 136016 : while (!OVERLOAD_TEST && isspace((unsigned char) *Np->input_p))
5063 : 119944 : Np->input_p++;
5064 : :
5065 : : /*
5066 : : * Collect and decode valid roman numerals, consuming at most
5067 : : * MAX_ROMAN_LEN characters. We do this in a separate loop to avoid
5068 : : * repeated decoding and because the main loop needs to know when it's at
5069 : : * the last numeral.
5070 : : */
582 tgl@sss.pgh.pa.us 5071 [ + + + + ]:CBC 136308 : for (len = 0; len < MAX_ROMAN_LEN && !OVERLOAD_TEST; len++)
5072 : : {
7 tgl@sss.pgh.pa.us 5073 :GNC 120252 : char currChar = pg_ascii_toupper(*Np->input_p);
582 tgl@sss.pgh.pa.us 5074 [ + + + + :CBC 120252 : int currValue = ROMAN_VAL(currChar);
+ + + + +
+ + + +
+ ]
5075 : :
5076 [ + + ]: 120252 : if (currValue == 0)
5077 : 16 : break; /* Not a valid roman numeral. */
5078 : 120236 : romanChars[len] = currChar;
5079 : 120236 : romanValues[len] = currValue;
7 tgl@sss.pgh.pa.us 5080 :GNC 120236 : Np->input_p++;
5081 : : }
5082 : :
582 tgl@sss.pgh.pa.us 5083 [ + + ]:CBC 16072 : if (len == 0)
5084 : 8 : return -1; /* No valid roman numerals. */
5085 : :
5086 : : /* Check for valid combinations and compute the represented value. */
302 peter@eisentraut.org 5087 [ + + ]: 126600 : for (size_t i = 0; i < len; i++)
5088 : : {
582 tgl@sss.pgh.pa.us 5089 : 110584 : char currChar = romanChars[i];
5090 : 110584 : int currValue = romanValues[i];
5091 : :
5092 : : /*
5093 : : * Ensure no numeral greater than or equal to the subtracted numeral
5094 : : * appears after a subtraction.
5095 : : */
5096 [ + + + + ]: 110584 : if (subtractionEncountered && currValue >= lastSubtractedValue)
5097 : 4 : return -1;
5098 : :
5099 : : /*
5100 : : * V, L, and D should not appear before a larger numeral, nor should
5101 : : * they be repeated.
5102 : : */
5103 [ + + + + : 110580 : if ((vCount && currValue >= ROMAN_VAL('V')) ||
+ + ]
5104 [ + - + + ]: 110576 : (lCount && currValue >= ROMAN_VAL('L')) ||
5105 [ - + ]: 38420 : (dCount && currValue >= ROMAN_VAL('D')))
5106 : 4 : return -1;
5107 [ + + ]: 110576 : if (currChar == 'V')
5108 : 6416 : vCount++;
5109 [ + + ]: 104160 : else if (currChar == 'L')
5110 : 6408 : lCount++;
5111 [ + + ]: 97752 : else if (currChar == 'D')
5112 : 6412 : dCount++;
5113 : :
5114 [ + + ]: 110576 : if (i < len - 1)
5115 : : {
5116 : : /* Compare current numeral to next numeral. */
5117 : 98120 : char nextChar = romanChars[i + 1];
5118 : 98120 : int nextValue = romanValues[i + 1];
5119 : :
5120 : : /*
5121 : : * If the current value is less than the next value, handle
5122 : : * subtraction. Verify valid subtractive combinations and update
5123 : : * the result accordingly.
5124 : : */
5125 [ + + ]: 98120 : if (currValue < nextValue)
5126 : : {
5127 [ + + + + : 9648 : if (!IS_VALID_SUB_COMB(currChar, nextChar))
+ + + + +
+ - + + +
+ + - + ]
5128 : 4 : return -1;
5129 : :
5130 : : /*
5131 : : * Reject cases where same numeral is repeated with
5132 : : * subtraction (e.g. 'MCCM' or 'DCCCD').
5133 : : */
5134 [ + + ]: 9644 : if (repeatCount > 1)
5135 : 8 : return -1;
5136 : :
5137 : : /*
5138 : : * We are going to skip nextChar, so first make checks needed
5139 : : * for V, L, and D. These are the same as we'd have applied
5140 : : * if we reached nextChar without a subtraction.
5141 : : */
5142 [ + + - + : 9636 : if ((vCount && nextValue >= ROMAN_VAL('V')) ||
+ + ]
5143 [ + + + + ]: 9628 : (lCount && nextValue >= ROMAN_VAL('L')) ||
5144 [ + + ]: 3208 : (dCount && nextValue >= ROMAN_VAL('D')))
5145 : 24 : return -1;
5146 [ + + ]: 9612 : if (nextChar == 'V')
5147 : 1608 : vCount++;
5148 [ + + ]: 8004 : else if (nextChar == 'L')
5149 : 1600 : lCount++;
5150 [ + + ]: 6404 : else if (nextChar == 'D')
5151 : 1600 : dCount++;
5152 : :
5153 : : /*
5154 : : * Skip the next numeral as it is part of the subtractive
5155 : : * combination.
5156 : : */
5157 : 9612 : i++;
5158 : :
5159 : : /* Update state. */
5160 : 9612 : repeatCount = 1;
5161 : 9612 : subtractionEncountered = true;
5162 : 9612 : lastSubtractedValue = currValue;
5163 : 9612 : result += (nextValue - currValue);
5164 : : }
5165 : : else
5166 : : {
5167 : : /* For same numerals, check for repetition. */
5168 [ + + ]: 88472 : if (currChar == nextChar)
5169 : : {
5170 : 40852 : repeatCount++;
5171 [ + + ]: 40852 : if (repeatCount > 3)
5172 : 4 : return -1;
5173 : : }
5174 : : else
5175 : 47620 : repeatCount = 1;
5176 : 88468 : result += currValue;
5177 : : }
5178 : : }
5179 : : else
5180 : : {
5181 : : /* This is the last numeral; just add it to the result. */
5182 : 12456 : result += currValue;
5183 : : }
5184 : : }
5185 : :
5186 : 16016 : return result;
5187 : : }
5188 : :
5189 : :
5190 : : /*
5191 : : * Locale
5192 : : */
5193 : : static void
9711 bruce@momjian.us 5194 : 699312 : NUM_prepare_locale(NUMProc *Np)
5195 : : {
4367 5196 [ + + ]: 699312 : if (Np->Num->need_locale)
5197 : : {
5198 : : struct lconv *lconv;
5199 : :
5200 : : /*
5201 : : * Get locales
5202 : : */
9711 5203 : 558 : lconv = PGLC_localeconv();
5204 : :
5205 : : /*
5206 : : * Positive / Negative number sign
5207 : : */
5208 [ + - - + ]: 558 : if (lconv->negative_sign && *lconv->negative_sign)
9711 bruce@momjian.us 5209 :UBC 0 : Np->L_negative_sign = lconv->negative_sign;
5210 : : else
9711 bruce@momjian.us 5211 :CBC 558 : Np->L_negative_sign = "-";
5212 : :
7501 5213 [ + - - + ]: 558 : if (lconv->positive_sign && *lconv->positive_sign)
9711 bruce@momjian.us 5214 :UBC 0 : Np->L_positive_sign = lconv->positive_sign;
5215 : : else
9633 bruce@momjian.us 5216 :CBC 558 : Np->L_positive_sign = "+";
5217 : :
5218 : : /*
5219 : : * Number decimal point
5220 : : */
9711 5221 [ + - + - ]: 558 : if (lconv->decimal_point && *lconv->decimal_point)
5222 : 558 : Np->decimal = lconv->decimal_point;
5223 : :
5224 : : else
9711 bruce@momjian.us 5225 :UBC 0 : Np->decimal = ".";
5226 : :
4367 bruce@momjian.us 5227 [ + + ]:CBC 558 : if (!IS_LDECIMAL(Np->Num))
7135 5228 : 472 : Np->decimal = ".";
5229 : :
5230 : : /*
5231 : : * Number thousands separator
5232 : : *
5233 : : * Some locales (e.g. broken glibc pt_BR), have a comma for decimal,
5234 : : * but "" for thousands_sep, so we set the thousands_sep too.
5235 : : * http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php
5236 : : */
5237 [ + - - + ]: 558 : if (lconv->thousands_sep && *lconv->thousands_sep)
7135 bruce@momjian.us 5238 :UBC 0 : Np->L_thousands_sep = lconv->thousands_sep;
5239 : : /* Make sure thousands separator doesn't match decimal point symbol. */
2294 tgl@sss.pgh.pa.us 5240 [ + - ]:CBC 558 : else if (strcmp(Np->decimal, ",") != 0)
7135 bruce@momjian.us 5241 : 558 : Np->L_thousands_sep = ",";
5242 : : else
6854 bruce@momjian.us 5243 :UBC 0 : Np->L_thousands_sep = ".";
5244 : :
5245 : : /*
5246 : : * Currency symbol
5247 : : */
9633 bruce@momjian.us 5248 [ + - - + ]:CBC 558 : if (lconv->currency_symbol && *lconv->currency_symbol)
9711 bruce@momjian.us 5249 :UBC 0 : Np->L_currency_symbol = lconv->currency_symbol;
5250 : : else
9633 bruce@momjian.us 5251 :CBC 558 : Np->L_currency_symbol = " ";
5252 : : }
5253 : : else
5254 : : {
5255 : : /*
5256 : : * Default values
5257 : : */
5258 : 698754 : Np->L_negative_sign = "-";
5259 : 698754 : Np->L_positive_sign = "+";
5260 : 698754 : Np->decimal = ".";
5261 : :
5262 : 698754 : Np->L_thousands_sep = ",";
5263 : 698754 : Np->L_currency_symbol = " ";
5264 : : }
9711 5265 : 699312 : }
5266 : :
5267 : : /*
5268 : : * Return pointer of last relevant number after decimal point
5269 : : * 12.0500 --> last relevant is '5'
5270 : : * 12.0000 --> last relevant is '.'
5271 : : * If there is no decimal point, return NULL (which will result in same
5272 : : * behavior as if FM hadn't been specified).
5273 : : */
5274 : : static const char *
302 peter@eisentraut.org 5275 : 452 : get_last_relevant_decnum(const char *num)
5276 : : {
5277 : : const char *result,
9289 bruce@momjian.us 5278 : 452 : *p = strchr(num, '.');
5279 : :
5280 : : #ifdef DEBUG_TO_FROM_CHAR
5281 : : elog(DEBUG_elog_output, "get_last_relevant_decnum()");
5282 : : #endif
5283 : :
9633 5284 [ + + ]: 452 : if (!p)
5468 tgl@sss.pgh.pa.us 5285 : 4 : return NULL;
5286 : :
9697 bruce@momjian.us 5287 : 448 : result = p;
5288 : :
9633 5289 [ + + ]: 6628 : while (*(++p))
5290 : : {
5291 [ + + ]: 6180 : if (*p != '0')
9697 5292 : 1296 : result = p;
5293 : : }
5294 : :
5295 : 448 : return result;
5296 : : }
5297 : :
5298 : :
5299 : : /*
5300 : : * Macros used by both TO_NUMBER() and TO_CHAR() code
5301 : : */
5302 : : #define NUM_EMITF(...) appendStringInfo(Np->output, __VA_ARGS__)
5303 : : #define NUM_EMITS(str) appendStringInfoString(Np->output, str)
5304 : : #define NUM_EMITC(chr) appendStringInfoChar(Np->output, chr)
5305 : :
5306 : : /*
5307 : : * Number extraction for TO_NUMBER()
5308 : : */
5309 : : static void
7 tgl@sss.pgh.pa.us 5310 :GNC 582 : NUM_numpart_from_char(NUMProc *Np, int id)
5311 : : {
3298 peter_e@gmx.net 5312 :CBC 582 : bool isread = false;
5313 : :
5314 : : #ifdef DEBUG_TO_FROM_CHAR
5315 : : elog(DEBUG_elog_output, " --- scan start --- id=%s",
5316 : : (id == NUM_0 || id == NUM_9) ? "NUM_0/9" : id == NUM_DEC ? "NUM_DEC" : "???");
5317 : : #endif
5318 : :
3671 5319 [ - + ]: 582 : if (OVERLOAD_TEST)
3671 peter_e@gmx.net 5320 :UBC 0 : return;
5321 : :
7 tgl@sss.pgh.pa.us 5322 [ - + ]:GNC 582 : if (*Np->input_p == ' ')
7 tgl@sss.pgh.pa.us 5323 :UNC 0 : Np->input_p++;
5324 : :
9668 bruce@momjian.us 5325 [ - + ]:CBC 582 : if (OVERLOAD_TEST)
9668 bruce@momjian.us 5326 :UBC 0 : return;
5327 : :
5328 : : /*
5329 : : * read sign before number
5330 : : */
7 tgl@sss.pgh.pa.us 5331 [ - + ]:GNC 582 : Assert(Np->output->len > 0);
5332 [ + + + - : 582 : if (Np->output->data[0] == ' ' && (id == NUM_0 || id == NUM_9) &&
+ + ]
7621 bruce@momjian.us 5333 [ + + ]:CBC 392 : (Np->read_pre + Np->read_post) == 0)
5334 : : {
5335 : : #ifdef DEBUG_TO_FROM_CHAR
5336 : : elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s",
5337 : : *Np->input_p, Np->L_positive_sign, Np->L_negative_sign);
5338 : : #endif
5339 : :
5340 : : /*
5341 : : * locale sign
5342 : : */
4367 5343 [ + + + + ]: 114 : if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_PRE)
9633 5344 : 8 : {
302 peter@eisentraut.org 5345 : 8 : size_t x = 0;
5346 : :
5347 : : #ifdef DEBUG_TO_FROM_CHAR
5348 : : elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *Np->input_p);
5349 : : #endif
7621 bruce@momjian.us 5350 [ + - ]: 8 : if ((x = strlen(Np->L_negative_sign)) &&
7973 tgl@sss.pgh.pa.us 5351 [ + - ]: 8 : AMOUNT_TEST(x) &&
7 tgl@sss.pgh.pa.us 5352 [ + + ]:GNC 8 : strncmp(Np->input_p, Np->L_negative_sign, x) == 0)
5353 : : {
5354 : 4 : Np->input_p += x;
5355 : 4 : Np->output->data[0] = '-';
5356 : : }
7621 bruce@momjian.us 5357 [ + - ]:CBC 4 : else if ((x = strlen(Np->L_positive_sign)) &&
5358 [ + - ]: 4 : AMOUNT_TEST(x) &&
7 tgl@sss.pgh.pa.us 5359 [ - + ]:GNC 4 : strncmp(Np->input_p, Np->L_positive_sign, x) == 0)
5360 : : {
7 tgl@sss.pgh.pa.us 5361 :UNC 0 : Np->input_p += x;
5362 : 0 : Np->output->data[0] = '+';
5363 : : }
5364 : : }
5365 : : else
5366 : : {
5367 : : #ifdef DEBUG_TO_FROM_CHAR
5368 : : elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->input_p);
5369 : : #endif
5370 : :
5371 : : /*
5372 : : * simple + - < >
5373 : : */
7 tgl@sss.pgh.pa.us 5374 [ + + + + ]:GNC 106 : if (*Np->input_p == '-' || (IS_BRACKET(Np->Num) &&
5375 [ + - ]: 4 : *Np->input_p == '<'))
5376 : : {
5377 : 12 : Np->output->data[0] = '-'; /* set - */
5378 : 12 : Np->input_p++;
5379 : : }
5380 [ - + ]: 94 : else if (*Np->input_p == '+')
5381 : : {
7 tgl@sss.pgh.pa.us 5382 :UNC 0 : Np->output->data[0] = '+'; /* set + */
5383 : 0 : Np->input_p++;
5384 : : }
5385 : : }
5386 : : }
5387 : :
9668 bruce@momjian.us 5388 [ - + ]:CBC 582 : if (OVERLOAD_TEST)
9668 bruce@momjian.us 5389 :UBC 0 : return;
5390 : :
5391 : : #ifdef DEBUG_TO_FROM_CHAR
5392 : : elog(DEBUG_elog_output, "Scan for numbers (%c), current output: '%s'", *Np->input_p, Np->output->data);
5393 : : #endif
5394 : :
5395 : : /*
5396 : : * read digit or decimal point
5397 : : */
7 tgl@sss.pgh.pa.us 5398 [ + + ]:GNC 582 : if (isdigit((unsigned char) *Np->input_p))
5399 : : {
4367 bruce@momjian.us 5400 [ + + - + ]:CBC 496 : if (Np->read_dec && Np->read_post == Np->Num->post)
9697 bruce@momjian.us 5401 :UBC 0 : return;
5402 : :
7 tgl@sss.pgh.pa.us 5403 :GNC 496 : NUM_EMITC(*Np->input_p);
5404 : :
9697 bruce@momjian.us 5405 [ + + ]:CBC 496 : if (Np->read_dec)
5406 : 174 : Np->read_post++;
5407 : : else
7973 tgl@sss.pgh.pa.us 5408 : 322 : Np->read_pre++;
5409 : :
3298 peter_e@gmx.net 5410 : 496 : isread = true;
5411 : :
5412 : : #ifdef DEBUG_TO_FROM_CHAR
5413 : : elog(DEBUG_elog_output, "Read digit (%c)", *Np->input_p);
5414 : : #endif
5415 : : }
5416 [ + + + + ]: 86 : else if (IS_DECIMAL(Np->Num) && Np->read_dec == false)
5417 : : {
5418 : : /*
5419 : : * We need not test IS_LDECIMAL(Np->Num) explicitly here, because
5420 : : * Np->decimal is always just "." if we don't have a D format token.
5421 : : * So we just unconditionally match to Np->decimal.
5422 : : */
302 peter@eisentraut.org 5423 : 78 : size_t x = strlen(Np->decimal);
5424 : :
5425 : : #ifdef DEBUG_TO_FROM_CHAR
5426 : : elog(DEBUG_elog_output, "Try read decimal point (%c)",
5427 : : *Np->input_p);
5428 : : #endif
7 tgl@sss.pgh.pa.us 5429 [ + - + - :GNC 78 : if (x && AMOUNT_TEST(x) && strncmp(Np->input_p, Np->decimal, x) == 0)
+ + ]
5430 : : {
5431 : 70 : Np->input_p += x - 1;
5432 : 70 : NUM_EMITC('.');
3298 peter_e@gmx.net 5433 :CBC 70 : Np->read_dec = true;
5434 : 70 : isread = true;
5435 : : }
5436 : : }
5437 : :
7973 tgl@sss.pgh.pa.us 5438 [ - + ]: 582 : if (OVERLOAD_TEST)
7973 tgl@sss.pgh.pa.us 5439 :UBC 0 : return;
5440 : :
5441 : : /*
5442 : : * Read sign behind "last" number
5443 : : *
5444 : : * We need sign detection because determine exact position of post-sign is
5445 : : * difficult:
5446 : : *
5447 : : * FM9999.9999999S -> 123.001- 9.9S -> .5- FM9.999999MI ->
5448 : : * 5.01-
5449 : : */
7 tgl@sss.pgh.pa.us 5450 [ + + + + ]:GNC 582 : if (Np->output->data[0] == ' ' && Np->read_pre + Np->read_post > 0)
5451 : : {
5452 : : /*
5453 : : * locale sign (NUM_S) is always anchored behind a last number, if: -
5454 : : * locale sign expected - last read char was NUM_0/9 or NUM_DEC - and
5455 : : * next char is not digit
5456 : : */
4367 bruce@momjian.us 5457 [ + + + - ]:CBC 410 : if (IS_LSIGN(Np->Num) && isread &&
7 tgl@sss.pgh.pa.us 5458 [ + - ]:GNC 102 : (Np->input_p + 1) < Np->input_end &&
5459 [ + + ]: 102 : !isdigit((unsigned char) *(Np->input_p + 1)))
7973 tgl@sss.pgh.pa.us 5460 :CBC 46 : {
5461 : : size_t x;
7 tgl@sss.pgh.pa.us 5462 :GNC 46 : const char *tmp = Np->input_p++;
5463 : :
5464 : : #ifdef DEBUG_TO_FROM_CHAR
5465 : : elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *Np->input_p);
5466 : : #endif
7621 bruce@momjian.us 5467 [ + - ]:CBC 46 : if ((x = strlen(Np->L_negative_sign)) &&
7973 tgl@sss.pgh.pa.us 5468 [ + - ]: 46 : AMOUNT_TEST(x) &&
7 tgl@sss.pgh.pa.us 5469 [ + + ]:GNC 46 : strncmp(Np->input_p, Np->L_negative_sign, x) == 0)
5470 : : {
5471 : 22 : Np->input_p += x - 1;
5472 : : /* NUM_processor_from_char() will do input_p++ */
5473 : 22 : Np->output->data[0] = '-';
5474 : : }
7621 bruce@momjian.us 5475 [ + - ]:CBC 24 : else if ((x = strlen(Np->L_positive_sign)) &&
5476 [ + - ]: 24 : AMOUNT_TEST(x) &&
7 tgl@sss.pgh.pa.us 5477 [ - + ]:GNC 24 : strncmp(Np->input_p, Np->L_positive_sign, x) == 0)
5478 : : {
7 tgl@sss.pgh.pa.us 5479 :UNC 0 : Np->input_p += x - 1;
5480 : : /* NUM_processor_from_char() will do input_p++ */
5481 : 0 : Np->output->data[0] = '+';
5482 : : }
7 tgl@sss.pgh.pa.us 5483 [ + + ]:GNC 46 : if (Np->output->data[0] == ' ')
5484 : : /* no sign read */
5485 : 24 : Np->input_p = tmp;
5486 : : }
5487 : :
5488 : : /*
5489 : : * try read non-locale sign, which happens only if format is not exact
5490 : : * and we cannot determine sign position of MI/PL/SG, an example:
5491 : : *
5492 : : * FM9.999999MI -> 5.01-
5493 : : *
5494 : : * if (.... && IS_LSIGN(Np->Num)==false) prevents read wrong formats
5495 : : * like to_number('1 -', '9S') where sign is not anchored to last
5496 : : * number.
5497 : : */
3298 peter_e@gmx.net 5498 [ + + + - ]:CBC 364 : else if (isread == false && IS_LSIGN(Np->Num) == false &&
4367 bruce@momjian.us 5499 [ + - + + ]: 16 : (IS_PLUS(Np->Num) || IS_MINUS(Np->Num)))
5500 : : {
5501 : : #ifdef DEBUG_TO_FROM_CHAR
5502 : : elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->input_p);
5503 : : #endif
5504 : :
5505 : : /*
5506 : : * simple + -
5507 : : */
7 tgl@sss.pgh.pa.us 5508 [ - + - - ]:GNC 4 : if (*Np->input_p == '-' || *Np->input_p == '+')
5509 : : {
5510 : 4 : Np->output->data[0] = *Np->input_p;
5511 : : /* NUM_processor_from_char() will do input_p++ */
5512 : : }
5513 : : }
5514 : : }
5515 : : }
5516 : :
5517 : : #define IS_PREDEC_SPACE(_n) \
5518 : : (IS_ZERO((_n)->Num)==false && \
5519 : : (_n)->input == (_n)->input_p && \
5520 : : *(_n)->input == '0' && \
5521 : : (_n)->Num->post != 0)
5522 : :
5523 : : /*
5524 : : * Add digit or sign to number-string
5525 : : */
5526 : : static void
9633 bruce@momjian.us 5527 :CBC 5898865 : NUM_numpart_to_char(NUMProc *Np, int id)
5528 : : {
5529 : : int end;
5530 : :
4367 5531 [ - + ]: 5898865 : if (IS_ROMAN(Np->Num))
9697 bruce@momjian.us 5532 :UBC 0 : return;
5533 : :
5534 : : #ifdef DEBUG_TO_FROM_CHAR
5535 : :
5536 : : /*
5537 : : * Np->num_curr is number of current item in format-picture, it is not
5538 : : * current position in output!
5539 : : */
5540 : : elog(DEBUG_elog_output,
5541 : : "SIGN_WROTE: %d, CURRENT: %d, INPUT_P: \"%s\", OUTPUT: \"%s\"",
5542 : : Np->sign_wrote,
5543 : : Np->num_curr,
5544 : : Np->input_p,
5545 : : Np->output->data);
5546 : : #endif
3298 peter_e@gmx.net 5547 :CBC 5898865 : Np->num_in = false;
5548 : :
5549 : : /*
5550 : : * Write sign if real number will write to output Note: IS_PREDEC_SPACE()
5551 : : * handle "9.9" --> " .1"
5552 : : */
5553 [ + + ]: 5898865 : if (Np->sign_wrote == false &&
4367 bruce@momjian.us 5554 [ + + + + : 9966 : (Np->num_curr >= Np->out_pre_spaces || (IS_ZERO(Np->Num) && Np->Num->zero_start == Np->num_curr)) &&
+ + + + ]
3298 peter_e@gmx.net 5555 [ + + + + : 2027 : (IS_PREDEC_SPACE(Np) == false || (Np->last_relevant && *Np->last_relevant == '.')))
+ + + + +
+ + - ]
5556 : : {
4367 bruce@momjian.us 5557 [ + + ]: 1931 : if (IS_LSIGN(Np->Num))
5558 : : {
5559 [ + + ]: 1290 : if (Np->Num->lsign == NUM_LSIGN_PRE)
5560 : : {
7 tgl@sss.pgh.pa.us 5561 [ + + ]:GNC 240 : NUM_EMITS((Np->sign == '-') ?
5562 : : Np->L_negative_sign :
5563 : : Np->L_positive_sign);
3298 peter_e@gmx.net 5564 :CBC 240 : Np->sign_wrote = true;
5565 : : }
5566 : : }
4367 bruce@momjian.us 5567 [ + + ]: 641 : else if (IS_BRACKET(Np->Num))
5568 : : {
7 tgl@sss.pgh.pa.us 5569 [ + + ]:GNC 96 : NUM_EMITC(Np->sign == '+' ? ' ' : '<');
3298 peter_e@gmx.net 5570 :CBC 96 : Np->sign_wrote = true;
5571 : : }
9633 bruce@momjian.us 5572 [ + + ]: 545 : else if (Np->sign == '+')
5573 : : {
4367 5574 [ + - ]: 365 : if (!IS_FILLMODE(Np->Num))
5575 : : {
7 tgl@sss.pgh.pa.us 5576 :GNC 365 : NUM_EMITC(' '); /* Write + */
5577 : : }
3298 peter_e@gmx.net 5578 :CBC 365 : Np->sign_wrote = true;
5579 : : }
9633 bruce@momjian.us 5580 [ + - ]: 180 : else if (Np->sign == '-')
5581 : : { /* Write - */
7 tgl@sss.pgh.pa.us 5582 :GNC 180 : NUM_EMITC('-');
3298 peter_e@gmx.net 5583 :CBC 180 : Np->sign_wrote = true;
5584 : : }
5585 : : }
5586 : :
5587 : :
5588 : : /*
5589 : : * digits / FM / Zero / Dec. point
5590 : : */
8554 bruce@momjian.us 5591 [ + + + + : 5898865 : if (id == NUM_9 || id == NUM_0 || id == NUM_D || id == NUM_DEC)
+ + + - ]
5592 : : {
4374 5593 [ + + ]: 5898865 : if (Np->num_curr < Np->out_pre_spaces &&
4367 5594 [ + + + + ]: 3567839 : (Np->Num->zero_start > Np->num_curr || !IS_ZERO(Np->Num)))
5595 : : {
5596 : : /*
5597 : : * Write blank space
5598 : : */
5599 [ + + ]: 10428 : if (!IS_FILLMODE(Np->Num))
5600 : : {
7 tgl@sss.pgh.pa.us 5601 :GNC 6592 : NUM_EMITC(' '); /* Write ' ' */
5602 : : }
5603 : : }
4367 bruce@momjian.us 5604 [ + + ]:CBC 5888437 : else if (IS_ZERO(Np->Num) &&
4374 5605 [ + + ]: 5870462 : Np->num_curr < Np->out_pre_spaces &&
4367 5606 [ + - ]: 3557411 : Np->Num->zero_start <= Np->num_curr)
5607 : : {
5608 : : /*
5609 : : * Write ZERO
5610 : : */
7 tgl@sss.pgh.pa.us 5611 :GNC 3557411 : NUM_EMITC('0'); /* Write '0' */
3298 peter_e@gmx.net 5612 :CBC 3557411 : Np->num_in = true;
5613 : : }
5614 : : else
5615 : : {
5616 : : /*
5617 : : * Write Decimal point
5618 : : */
7 tgl@sss.pgh.pa.us 5619 [ + + ]:GNC 2331026 : if (*Np->input_p == '.')
5620 : : {
9633 bruce@momjian.us 5621 [ + + + + ]:CBC 1044 : if (!Np->last_relevant || *Np->last_relevant != '.')
5622 : : {
7 tgl@sss.pgh.pa.us 5623 :GNC 924 : NUM_EMITS(Np->decimal); /* Write DEC/D */
5624 : : }
5625 : :
5626 : : /*
5627 : : * Ora 'n' -- FM9.9 --> 'n.'
5628 : : */
4367 bruce@momjian.us 5629 [ + - ]:CBC 120 : else if (IS_FILLMODE(Np->Num) &&
9633 5630 [ + - + - ]: 120 : Np->last_relevant && *Np->last_relevant == '.')
5631 : : {
7 tgl@sss.pgh.pa.us 5632 :GNC 120 : NUM_EMITS(Np->decimal); /* Write DEC/D */
5633 : : }
5634 : : }
5635 : : else
5636 : : {
5637 : : /*
5638 : : * Write Digits
5639 : : */
5640 [ + + + + : 2329982 : if (Np->last_relevant && Np->input_p > Np->last_relevant &&
+ + ]
5641 : : id != NUM_0)
5642 : : ;
5643 : :
5644 : : /*
5645 : : * '0.1' -- 9.9 --> ' .1'
5646 : : */
8554 bruce@momjian.us 5647 [ + + + + :CBC 2325534 : else if (IS_PREDEC_SPACE(Np))
+ + + + ]
5648 : : {
4367 5649 [ + + ]: 152 : if (!IS_FILLMODE(Np->Num))
5650 : : {
7 tgl@sss.pgh.pa.us 5651 :GNC 104 : NUM_EMITC(' ');
5652 : : }
5653 : :
5654 : : /*
5655 : : * '0' -- FM9.9 --> '0.'
5656 : : */
9633 bruce@momjian.us 5657 [ + - + + ]:CBC 48 : else if (Np->last_relevant && *Np->last_relevant == '.')
5658 : : {
7 tgl@sss.pgh.pa.us 5659 :GNC 40 : NUM_EMITC('0');
5660 : : }
5661 : : }
5662 : : else
5663 : : {
5664 : 2325382 : NUM_EMITC(*Np->input_p); /* Write DIGIT */
3298 peter_e@gmx.net 5665 :CBC 2325382 : Np->num_in = true;
5666 : : }
5667 : : }
5668 : : /* do no exceed string length */
7 tgl@sss.pgh.pa.us 5669 [ + + ]:GNC 2331026 : if (*Np->input_p)
5670 : 2330798 : ++Np->input_p;
5671 : : }
5672 : :
4367 bruce@momjian.us 5673 :CBC 5898865 : end = Np->num_count + (Np->out_pre_spaces ? 1 : 0) + (IS_DECIMAL(Np->Num) ? 1 : 0);
5674 : :
7 tgl@sss.pgh.pa.us 5675 [ + + + + ]:GNC 5898865 : if (Np->last_relevant && Np->last_relevant == Np->input_p)
8554 bruce@momjian.us 5676 :CBC 448 : end = Np->num_curr;
5677 : :
8424 5678 [ + + ]: 5898865 : if (Np->num_curr + 1 == end)
5679 : : {
3298 peter_e@gmx.net 5680 [ + + + + ]: 667034 : if (Np->sign_wrote == true && IS_BRACKET(Np->Num))
5681 : : {
7 tgl@sss.pgh.pa.us 5682 [ + + ]:GNC 96 : NUM_EMITC(Np->sign == '+' ? ' ' : '>');
5683 : : }
4367 bruce@momjian.us 5684 [ + + + + ]:CBC 666938 : else if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_POST)
5685 : : {
7 tgl@sss.pgh.pa.us 5686 [ + + ]:GNC 61 : NUM_EMITS((Np->sign == '-') ?
5687 : : Np->L_negative_sign :
5688 : : Np->L_positive_sign);
5689 : : }
5690 : : }
5691 : : }
5692 : :
9697 bruce@momjian.us 5693 :CBC 5898865 : ++Np->num_curr;
5694 : : }
5695 : :
5696 : : /*
5697 : : * Skip over "n" input characters, but only if they aren't numeric data
5698 : : */
5699 : : static void
7 tgl@sss.pgh.pa.us 5700 :GNC 24 : NUM_eat_non_data_chars(NUMProc *Np, int n)
5701 : : {
5702 : 24 : const char *end = Np->input_end;
5703 : :
3205 tgl@sss.pgh.pa.us 5704 [ + + ]:CBC 44 : while (n-- > 0)
5705 : : {
5706 [ - + ]: 28 : if (OVERLOAD_TEST)
3205 tgl@sss.pgh.pa.us 5707 :UBC 0 : break; /* end of input */
7 tgl@sss.pgh.pa.us 5708 [ + + ]:GNC 28 : if (strchr("0123456789.,+-", *Np->input_p) != NULL)
3205 tgl@sss.pgh.pa.us 5709 :CBC 8 : break; /* it's a data character */
7 tgl@sss.pgh.pa.us 5710 :GNC 20 : Np->input_p += pg_mblen_range(Np->input_p, end);
5711 : : }
3205 tgl@sss.pgh.pa.us 5712 :CBC 24 : }
5713 : :
5714 : : /*
5715 : : * Numeric format processing for TO_NUMBER.
5716 : : *
5717 : : * We parse the string in "input" according to the format, and build a
5718 : : * standard-format representation of the number in "output" (which will
5719 : : * be fed to numeric_in()).
5720 : : *
5721 : : * node: array of FormatNodes representing the parsed format string
5722 : : * Num: input/output argument holding additional format flags and state
5723 : : * input: input string (not null-terminated!)
5724 : : * input_len: length of input string
5725 : : * output: output buffer (must be empty initially!)
5726 : : * collid: active collation
5727 : : */
5728 : : static void
7 tgl@sss.pgh.pa.us 5729 :GNC 16174 : NUM_processor_from_char(const FormatNode *node, NUMDesc *Num,
5730 : : const char *input, size_t input_len,
5731 : : StringInfo output,
5732 : : Oid collid)
5733 : : {
5734 : : NUMProc _Np,
9289 bruce@momjian.us 5735 :CBC 16174 : *Np = &_Np;
5736 : : const char *pattern;
5737 : : size_t pattern_len;
5738 : :
8548 tgl@sss.pgh.pa.us 5739 [ + - + - : 274958 : MemSet(Np, 0, sizeof(NUMProc));
+ - + - +
+ ]
5740 : :
4367 bruce@momjian.us 5741 : 16174 : Np->Num = Num;
7 tgl@sss.pgh.pa.us 5742 :GNC 16174 : Np->input = input;
5743 : 16174 : Np->input_end = input + input_len;
5744 : 16174 : Np->output = output;
9697 bruce@momjian.us 5745 :CBC 16174 : Np->last_relevant = NULL;
9633 5746 : 16174 : Np->read_post = 0;
7621 5747 : 16174 : Np->read_pre = 0;
3298 peter_e@gmx.net 5748 : 16174 : Np->read_dec = false;
5749 : :
4367 bruce@momjian.us 5750 [ - + ]: 16174 : if (Np->Num->zero_start)
4367 bruce@momjian.us 5751 :LBC (665752) : --Np->Num->zero_start;
5752 : :
4367 bruce@momjian.us 5753 [ - + ]:CBC 16174 : if (IS_EEEE(Np->Num))
7 tgl@sss.pgh.pa.us 5754 [ # # ]:UNC 0 : ereport(ERROR,
5755 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5756 : : errmsg("\"EEEE\" not supported for input")));
5757 : :
5758 : : /*
5759 : : * Sign
5760 : : */
7 tgl@sss.pgh.pa.us 5761 :GNC 16174 : Np->sign = false;
5762 : :
5763 : : /*
5764 : : * Count
5765 : : */
4367 bruce@momjian.us 5766 :CBC 16174 : Np->num_count = Np->Num->post + Np->Num->pre - 1;
5767 : :
5768 : : /*
5769 : : * Initialize first character of output buffer with a space. Later, we
5770 : : * may overwrite that with '+' or '-'.
5771 : : */
7 tgl@sss.pgh.pa.us 5772 [ - + ]:GNC 16174 : Assert(Np->output->len == 0);
5773 : 16174 : NUM_EMITC(' ');
5774 : :
9633 bruce@momjian.us 5775 :CBC 16174 : Np->num_in = 0;
5776 : 16174 : Np->num_curr = 0;
5777 : :
5778 : : #ifdef DEBUG_TO_FROM_CHAR
5779 : : elog(DEBUG_elog_output,
5780 : : "\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s\n\tEEEE: %s",
5781 : : Np->sign,
5782 : : Np->output->data,
5783 : : Np->Num->pre,
5784 : : Np->Num->post,
5785 : : Np->num_count,
5786 : : Np->out_pre_spaces,
5787 : : Np->sign_wrote ? "Yes" : "No",
5788 : : IS_ZERO(Np->Num) ? "Yes" : "No",
5789 : : Np->Num->zero_start,
5790 : : Np->Num->zero_end,
5791 : : Np->last_relevant ? Np->last_relevant : "<not set>",
5792 : : IS_BRACKET(Np->Num) ? "Yes" : "No",
5793 : : IS_PLUS(Np->Num) ? "Yes" : "No",
5794 : : IS_MINUS(Np->Num) ? "Yes" : "No",
5795 : : IS_FILLMODE(Np->Num) ? "Yes" : "No",
5796 : : IS_ROMAN(Np->Num) ? "Yes" : "No",
5797 : : IS_EEEE(Np->Num) ? "Yes" : "No"
5798 : : );
5799 : : #endif
5800 : :
5801 : : /*
5802 : : * Locale
5803 : : */
9711 5804 : 16174 : NUM_prepare_locale(Np);
5805 : :
5806 : : /*
5807 : : * Processor direct cycle
5808 : : */
7 tgl@sss.pgh.pa.us 5809 :GNC 16174 : Np->input_p = Np->input;
5810 : :
5811 [ + + ]: 32938 : for (const FormatNode *n = node; n->type != NODE_TYPE_END; n++)
5812 : : {
5813 : : /*
5814 : : * Check at least one byte remains to be scanned. (In actions below,
5815 : : * must use AMOUNT_TEST if we want to read more bytes than that.)
5816 : : */
5817 [ + + ]: 16878 : if (OVERLOAD_TEST)
5818 : 58 : break;
5819 : :
5820 : : /*
5821 : : * Format pictures actions
5822 : : */
9633 bruce@momjian.us 5823 [ + + ]:CBC 16820 : if (n->type == NODE_TYPE_ACTION)
5824 : : {
5825 : : /*
5826 : : * Create/read digit/zero/blank/sign/special-case
5827 : : *
5828 : : * 'NUM_S' note: The locale sign is anchored to output and we
5829 : : * read/write it when we work with first or last number
5830 : : * (NUM_0/NUM_9). This is why NUM_S is missing in switch().
5831 : : *
5832 : : * Notice the "Np->input_p++" at the bottom of the loop. This is
5833 : : * why most of the actions advance input_p one less than you might
5834 : : * expect. In cases where we don't want that increment to happen,
5835 : : * a switch case ends with "continue" not "break".
5836 : : */
5837 [ + + + + : 16760 : switch (n->key->id)
+ + - - -
- + ]
5838 : : {
5839 : 582 : case NUM_9:
5840 : : case NUM_0:
5841 : : case NUM_DEC:
5842 : : case NUM_D:
7 tgl@sss.pgh.pa.us 5843 :GNC 582 : NUM_numpart_from_char(Np, n->key->id);
5844 : 582 : break; /* switch() case: */
5845 : :
9633 bruce@momjian.us 5846 :CBC 24 : case NUM_COMMA:
7 tgl@sss.pgh.pa.us 5847 [ + - ]:GNC 24 : if (!Np->num_in)
5848 : : {
5849 [ - + ]: 24 : if (IS_FILLMODE(Np->Num))
3205 tgl@sss.pgh.pa.us 5850 :LBC (24) : continue;
5851 : : }
7 tgl@sss.pgh.pa.us 5852 [ + - ]:GNC 24 : if (*Np->input_p != ',')
5853 : 24 : continue;
9633 bruce@momjian.us 5854 :LBC (220) : break;
5855 : :
9633 bruce@momjian.us 5856 :CBC 34 : case NUM_G:
3205 tgl@sss.pgh.pa.us 5857 : 34 : pattern = Np->L_thousands_sep;
5858 : 34 : pattern_len = strlen(pattern);
7 tgl@sss.pgh.pa.us 5859 [ + - ]:GNC 34 : if (!Np->num_in)
5860 : : {
5861 [ - + ]: 34 : if (IS_FILLMODE(Np->Num))
3205 tgl@sss.pgh.pa.us 5862 :LBC (4) : continue;
5863 : : }
5864 : :
5865 : : /*
5866 : : * Because L_thousands_sep typically contains data
5867 : : * characters (either '.' or ','), we can't use
5868 : : * NUM_eat_non_data_chars here. Instead skip only if the
5869 : : * input matches L_thousands_sep.
5870 : : */
7 tgl@sss.pgh.pa.us 5871 [ + - ]:GNC 34 : if (AMOUNT_TEST(pattern_len) &&
5872 [ + + ]: 34 : strncmp(Np->input_p, pattern, pattern_len) == 0)
5873 : 30 : Np->input_p += pattern_len - 1;
5874 : : else
3205 tgl@sss.pgh.pa.us 5875 :CBC 4 : continue;
9633 bruce@momjian.us 5876 : 30 : break;
5877 : :
7 tgl@sss.pgh.pa.us 5878 :GNC 20 : case NUM_L:
5879 : 20 : pattern = Np->L_currency_symbol;
5880 : 20 : NUM_eat_non_data_chars(Np, pg_mbstrlen(pattern));
5881 : 20 : continue;
5882 : :
9633 bruce@momjian.us 5883 :CBC 16072 : case NUM_RN:
5884 : : case NUM_rn:
9031 ishii@postgresql.org 5885 :GIC 16016 : {
7 tgl@sss.pgh.pa.us 5886 :GNC 16072 : int roman_result = roman_to_int(Np);
5887 : : int oldlen;
5888 : : int numlen;
5889 : :
582 tgl@sss.pgh.pa.us 5890 [ + + ]:CBC 16072 : if (roman_result < 0)
5891 [ + - ]: 56 : ereport(ERROR,
5892 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
5893 : : errmsg("invalid Roman numeral")));
7 tgl@sss.pgh.pa.us 5894 :GNC 16016 : oldlen = Np->output->len;
5895 : 16016 : NUM_EMITF("%d", roman_result);
5896 : 16016 : numlen = Np->output->len - oldlen;
582 tgl@sss.pgh.pa.us 5897 :CBC 16016 : Np->Num->pre = numlen;
5898 : 16016 : Np->Num->post = 0;
5899 : 16016 : continue; /* roman_to_int ate all the chars */
5900 : : }
5901 : :
9633 bruce@momjian.us 5902 : 4 : case NUM_th:
7 tgl@sss.pgh.pa.us 5903 [ + - + - ]:GNC 4 : if (IS_ROMAN(Np->Num) || Np->output->data[0] == '#' ||
4367 bruce@momjian.us 5904 [ + - - + ]:CBC 4 : Np->sign == '-' || IS_DECIMAL(Np->Num))
9633 bruce@momjian.us 5905 :LBC (44) : continue;
5906 : : /* All variants of 'th' occupy 2 characters */
7 tgl@sss.pgh.pa.us 5907 :GNC 4 : NUM_eat_non_data_chars(Np, 2);
5908 : 4 : continue;
5909 : :
7 tgl@sss.pgh.pa.us 5910 :LBC (60) : case NUM_TH:
7 tgl@sss.pgh.pa.us 5911 [ # # # # ]:UNC 0 : if (IS_ROMAN(Np->Num) || Np->output->data[0] == '#' ||
7 tgl@sss.pgh.pa.us 5912 [ # # # # ]:LBC (60) : Np->sign == '-' || IS_DECIMAL(Np->Num))
5913 : (44) : continue;
5914 : : /* All variants of 'TH' occupy 2 characters */
7 tgl@sss.pgh.pa.us 5915 :UNC 0 : NUM_eat_non_data_chars(Np, 2);
5916 : 0 : continue;
5917 : :
7 tgl@sss.pgh.pa.us 5918 :LBC (228) : case NUM_MI:
7 tgl@sss.pgh.pa.us 5919 [ # # ]:UNC 0 : if (*Np->input_p == '-')
5920 : 0 : Np->output->data[0] = '-';
5921 : : else
5922 : : {
5923 : 0 : NUM_eat_non_data_chars(Np, 1);
3205 5924 : 0 : continue;
5925 : : }
9633 bruce@momjian.us 5926 :LBC (228) : break;
5927 : :
7 tgl@sss.pgh.pa.us 5928 : (20) : case NUM_PL:
7 tgl@sss.pgh.pa.us 5929 [ # # ]:UNC 0 : if (*Np->input_p == '+')
5930 : 0 : Np->output->data[0] = '+';
5931 : : else
5932 : : {
5933 : 0 : NUM_eat_non_data_chars(Np, 1);
5934 : 0 : continue;
5935 : : }
7 tgl@sss.pgh.pa.us 5936 :LBC (20) : break;
5937 : :
5938 : (120) : case NUM_SG:
7 tgl@sss.pgh.pa.us 5939 [ # # ]:UNC 0 : if (*Np->input_p == '-')
5940 : 0 : Np->output->data[0] = '-';
5941 [ # # ]: 0 : else if (*Np->input_p == '+')
5942 : 0 : Np->output->data[0] = '+';
5943 : : else
5944 : : {
5945 : 0 : NUM_eat_non_data_chars(Np, 1);
3205 5946 : 0 : continue;
5947 : : }
9633 bruce@momjian.us 5948 :LBC (120) : break;
5949 : :
7 tgl@sss.pgh.pa.us 5950 :CBC 24 : default:
5951 : 24 : continue;
5952 : : break;
5953 : : }
5954 : : }
5955 : : else
5956 : : {
5957 : : /*
5958 : : * In TO_NUMBER, we skip one input character for each non-pattern
5959 : : * format character, whether or not it matches the format
5960 : : * character.
5961 : : */
7 tgl@sss.pgh.pa.us 5962 :GNC 60 : Np->input_p += pg_mblen_range(Np->input_p, Np->input_end);
7 tgl@sss.pgh.pa.us 5963 :CBC 60 : continue;
5964 : : }
7 tgl@sss.pgh.pa.us 5965 :GNC 612 : Np->input_p++;
5966 : : }
5967 : :
5968 : : /*
5969 : : * Truncate any final '.'; we know output string is not empty
5970 : : */
5971 [ - + ]: 16118 : if (Np->output->data[Np->output->len - 1] == '.')
7 tgl@sss.pgh.pa.us 5972 :UNC 0 : Np->output->data[--Np->output->len] = '\0';
5973 : :
5974 : : /*
5975 : : * Correction - precision of dec. number
5976 : : */
7 tgl@sss.pgh.pa.us 5977 :GNC 16118 : Np->Num->post = Np->read_post;
5978 : :
5979 : : #ifdef DEBUG_TO_FROM_CHAR
5980 : : elog(DEBUG_elog_output, "TO_NUMBER (output): '%s'", Np->output->data);
5981 : : #endif
5982 : 16118 : }
5983 : :
5984 : : /*
5985 : : * Numeric format processing for TO_CHAR.
5986 : : *
5987 : : * We generate a string in "output" according to the format, working from
5988 : : * the datatype-independent representation of the number in "input".
5989 : : *
5990 : : * node: array of FormatNodes representing the parsed format string
5991 : : * Num: input/output argument holding additional format flags and state
5992 : : * input: the value to be formatted
5993 : : * output: output buffer (results are appended to whatever is there)
5994 : : * out_pre_spaces: number of spaces needed before first digit
5995 : : * sign: '+' or '-'
5996 : : * collid: active collation
5997 : : *
5998 : : * DOCUMENTME: "input" is mostly in standard format, but the caller is
5999 : : * expected to have made some adjustments to it to simplify the logic here.
6000 : : * Should reverse-engineer and document the rules.
6001 : : */
6002 : : static void
6003 : 683362 : NUM_processor_to_char(const FormatNode *node, NUMDesc *Num,
6004 : : const char *input, StringInfo output,
6005 : : int out_pre_spaces, int sign, Oid collid)
6006 : : {
6007 : : NUMProc _Np,
6008 : 683362 : *Np = &_Np;
6009 : : const char *pattern;
6010 : :
6011 [ + - + - : 11617154 : MemSet(Np, 0, sizeof(NUMProc));
+ - + - +
+ ]
6012 : :
6013 : 683362 : Np->Num = Num;
6014 : 683362 : Np->input = input;
6015 : 683362 : Np->output = output;
6016 : 683362 : Np->last_relevant = NULL;
6017 : 683362 : Np->out_pre_spaces = out_pre_spaces;
6018 : 683362 : Np->read_post = 0;
6019 : 683362 : Np->read_pre = 0;
6020 : 683362 : Np->read_dec = false;
6021 : :
6022 [ + + ]: 683362 : if (Np->Num->zero_start)
6023 : 665752 : --Np->Num->zero_start;
6024 : :
6025 [ + + ]: 683362 : if (IS_EEEE(Np->Num))
6026 : : {
6027 : : /* In EEEE mode, we just regurgitate input as-is */
6028 : 224 : NUM_EMITS(input);
6029 : 224 : return;
6030 : : }
6031 : :
6032 : : /*
6033 : : * Sign
6034 : : */
6035 : 683138 : Np->sign = sign;
6036 : :
6037 : : /* MI/PL/SG - write sign itself and not in number */
6038 [ + + + + ]: 683138 : if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))
6039 : : {
6040 [ + + + + ]: 368 : if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == false)
6041 : 20 : Np->sign_wrote = false; /* need sign */
6042 : : else
6043 : 348 : Np->sign_wrote = true; /* needn't sign */
6044 : : }
6045 : : else
6046 : : {
6047 [ + + ]: 682770 : if (Np->sign != '-')
6048 : : {
6049 [ + + ]: 682421 : if (IS_FILLMODE(Np->Num))
6050 : 665892 : Np->Num->flag &= ~NUM_F_BRACKET;
6051 : : }
6052 : :
6053 [ + + + + : 682770 : if (Np->sign == '+' && IS_FILLMODE(Np->Num) && IS_LSIGN(Np->Num) == false)
+ + ]
6054 : 665756 : Np->sign_wrote = true; /* needn't sign */
6055 : : else
6056 : 17014 : Np->sign_wrote = false; /* need sign */
6057 : :
6058 [ + + + + ]: 682770 : if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num)
6059 : 20 : Np->Num->lsign = NUM_LSIGN_POST;
6060 : : }
6061 : :
6062 : : /*
6063 : : * Count
6064 : : */
6065 : 683138 : Np->num_count = Np->Num->post + Np->Num->pre - 1;
6066 : :
6067 [ + + + + ]: 683138 : if (IS_FILLMODE(Np->Num) && IS_DECIMAL(Np->Num))
6068 : : {
6069 : 452 : Np->last_relevant = get_last_relevant_decnum(Np->input);
6070 : :
6071 : : /*
6072 : : * If any '0' specifiers are present, make sure we don't strip those
6073 : : * digits. But don't advance last_relevant beyond the last character
6074 : : * of the Np->input string, which is a hazard if the number got
6075 : : * shortened due to precision limitations.
6076 : : */
6077 [ + + + + ]: 452 : if (Np->last_relevant && Np->Num->zero_end > Np->out_pre_spaces)
6078 : : {
6079 : : size_t last_zero_pos;
6080 : : const char *last_zero;
6081 : :
6082 : : /* note that Np->input cannot be zero-length here */
6083 : 184 : last_zero_pos = strlen(Np->input) - 1;
6084 : 184 : last_zero_pos = Min(last_zero_pos,
6085 : : Np->Num->zero_end - Np->out_pre_spaces);
6086 : 184 : last_zero = Np->input + last_zero_pos;
6087 [ + + ]: 184 : if (Np->last_relevant < last_zero)
6088 : 96 : Np->last_relevant = last_zero;
6089 : : }
6090 : : }
6091 : :
6092 [ + + + + ]: 683138 : if (Np->sign_wrote == false && Np->out_pre_spaces == 0)
6093 : 16354 : ++Np->num_count;
6094 : :
6095 : 683138 : Np->num_in = 0;
6096 : 683138 : Np->num_curr = 0;
6097 : :
6098 : : #ifdef DEBUG_TO_FROM_CHAR
6099 : : elog(DEBUG_elog_output,
6100 : : "\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s\n\tEEEE: %s",
6101 : : Np->sign,
6102 : : Np->input,
6103 : : Np->Num->pre,
6104 : : Np->Num->post,
6105 : : Np->num_count,
6106 : : Np->out_pre_spaces,
6107 : : Np->sign_wrote ? "Yes" : "No",
6108 : : IS_ZERO(Np->Num) ? "Yes" : "No",
6109 : : Np->Num->zero_start,
6110 : : Np->Num->zero_end,
6111 : : Np->last_relevant ? Np->last_relevant : "<not set>",
6112 : : IS_BRACKET(Np->Num) ? "Yes" : "No",
6113 : : IS_PLUS(Np->Num) ? "Yes" : "No",
6114 : : IS_MINUS(Np->Num) ? "Yes" : "No",
6115 : : IS_FILLMODE(Np->Num) ? "Yes" : "No",
6116 : : IS_ROMAN(Np->Num) ? "Yes" : "No",
6117 : : IS_EEEE(Np->Num) ? "Yes" : "No"
6118 : : );
6119 : : #endif
6120 : :
6121 : : /*
6122 : : * Locale
6123 : : */
6124 : 683138 : NUM_prepare_locale(Np);
6125 : :
6126 : : /*
6127 : : * Processor direct cycle
6128 : : */
6129 : 683138 : Np->input_p = Np->input;
6130 : :
6131 [ + + ]: 7271840 : for (const FormatNode *n = node; n->type != NODE_TYPE_END; n++)
6132 : : {
6133 : : /*
6134 : : * Format pictures actions
6135 : : */
6136 [ + + ]: 6588702 : if (n->type == NODE_TYPE_ACTION)
6137 : : {
6138 : : /*
6139 : : * Create/read digit/zero/blank/sign/special-case
6140 : : *
6141 : : * 'NUM_S' note: The locale sign is anchored to input and we
6142 : : * read/write it when we work with first or last number
6143 : : * (NUM_0/NUM_9). This is why NUM_S is missing in switch().
6144 : : */
6145 [ + + + + : 6582994 : switch (n->key->id)
+ + + + +
+ + ]
6146 : : {
6147 : 5898865 : case NUM_9:
6148 : : case NUM_0:
6149 : : case NUM_DEC:
6150 : : case NUM_D:
6151 : 5898865 : NUM_numpart_to_char(Np, n->key->id);
6152 : 5898865 : break;
6153 : :
6154 : 220 : case NUM_COMMA:
6155 [ + + ]: 220 : if (!Np->num_in)
6156 : : {
6157 [ + - ]: 80 : if (!IS_FILLMODE(Np->Num))
6158 : 80 : NUM_EMITC(' ');
6159 : : }
6160 : : else
6161 : 140 : NUM_EMITC(',');
9633 bruce@momjian.us 6162 : 220 : break;
6163 : :
7 tgl@sss.pgh.pa.us 6164 : 780 : case NUM_G:
6165 : 780 : pattern = Np->L_thousands_sep;
6166 [ + + ]: 780 : if (!Np->num_in)
6167 : : {
6168 [ + - ]: 392 : if (!IS_FILLMODE(Np->Num))
6169 : 392 : appendStringInfoSpaces(Np->output,
6170 : : pg_mbstrlen(pattern));
6171 : : }
6172 : : else
6173 : : {
6174 : 388 : NUM_EMITS(pattern);
6175 : : }
9633 bruce@momjian.us 6176 : 780 : break;
6177 : :
7 tgl@sss.pgh.pa.us 6178 : 60 : case NUM_L:
6179 : 60 : pattern = Np->L_currency_symbol;
6180 : 60 : NUM_EMITS(pattern);
6181 : 60 : break;
6182 : :
6183 : 16088 : case NUM_RN:
6184 : : case NUM_rn:
6185 : : {
6186 : : const char *input_p;
6187 : :
6188 [ + + ]: 16088 : if (n->key->id == NUM_rn)
6189 : 20 : input_p = asc_tolower_z(Np->input_p);
6190 : : else
6191 : 16068 : input_p = Np->input_p;
6192 [ + + ]: 16088 : if (IS_FILLMODE(Np->Num))
6193 : 64 : NUM_EMITS(input_p);
6194 : : else
6195 : 16024 : NUM_EMITF("%15s", input_p);
6196 : : }
9633 bruce@momjian.us 6197 : 16088 : break;
6198 : :
7 tgl@sss.pgh.pa.us 6199 : 60 : case NUM_th:
6200 [ + - + - ]: 60 : if (IS_ROMAN(Np->Num) || *Np->input == '#' ||
6201 [ + + + + ]: 60 : Np->sign == '-' || IS_DECIMAL(Np->Num))
6202 : : break;
6203 : 16 : NUM_EMITS(get_th(Np->input, TH_LOWER));
6204 : 16 : break;
6205 : :
6206 : 60 : case NUM_TH:
6207 [ + - + - ]: 60 : if (IS_ROMAN(Np->Num) || *Np->input == '#' ||
6208 [ + + + + ]: 60 : Np->sign == '-' || IS_DECIMAL(Np->Num))
6209 : : break;
6210 : 16 : NUM_EMITS(get_th(Np->input, TH_UPPER));
6211 : 16 : break;
6212 : :
6213 : 228 : case NUM_MI:
6214 [ + + ]: 228 : if (Np->sign == '-')
6215 : 64 : NUM_EMITC('-');
6216 [ + - ]: 164 : else if (!IS_FILLMODE(Np->Num))
6217 : 164 : NUM_EMITC(' ');
6218 : 228 : break;
6219 : :
6220 : 20 : case NUM_PL:
6221 [ + + ]: 20 : if (Np->sign == '+')
6222 : 16 : NUM_EMITC('+');
6223 [ + - ]: 4 : else if (!IS_FILLMODE(Np->Num))
6224 : 4 : NUM_EMITC(' ');
6225 : 20 : break;
6226 : :
6227 : 120 : case NUM_SG:
6228 : 120 : NUM_EMITC(Np->sign);
6229 : 120 : break;
6230 : :
9633 bruce@momjian.us 6231 : 666493 : default:
6232 : 666493 : break;
6233 : : }
6234 : : }
6235 : : else
6236 : : {
6237 : : /*
6238 : : * In TO_CHAR, non-pattern characters in the format are copied to
6239 : : * the output.
6240 : : */
7 tgl@sss.pgh.pa.us 6241 : 5708 : NUM_EMITS(n->character);
6242 : : }
6243 : : }
6244 : : }
6245 : :
6246 : : /*
6247 : : * MACRO: Start part of NUM - for all NUM's to_char variants
6248 : : * (sorry, but I hate copy same code - macro is better..)
6249 : : */
6250 : : #define NUM_TOCHAR_prepare \
6251 : : do { \
6252 : : int len = VARSIZE_ANY_EXHDR(fmt); \
6253 : : if (len <= 0) /* easy case for empty format */ \
6254 : : PG_RETURN_TEXT_P(cstring_to_text("")); \
6255 : : format = NUM_cache(len, &Num, fmt, &shouldFree); \
6256 : : } while (0)
6257 : :
6258 : : /*
6259 : : * MACRO: Finish part of NUM
6260 : : */
6261 : : #define NUM_TOCHAR_finish \
6262 : : do { \
6263 : : /* \
6264 : : * Create workspace to hold result. We'll use result.data directly as the \
6265 : : * returned TEXT datum, so leave enough room for the varlena header. \
6266 : : * Temporarily fill that area with spaces; that's not really necessary but \
6267 : : * it eases debugging by ensuring the result string is always printable. \
6268 : : */ \
6269 : : initStringInfo(&result); \
6270 : : enlargeStringInfo(&result, VARHDRSZ); /* just pro-forma */ \
6271 : : memset(result.data, ' ', VARHDRSZ); \
6272 : : result.len = VARHDRSZ; \
6273 : : result.data[VARHDRSZ] = '\0'; /* maintain StringInfo's invariant */ \
6274 : : \
6275 : : NUM_processor_to_char(format, &Num, numstr, &result, \
6276 : : out_pre_spaces, sign, PG_GET_COLLATION()); \
6277 : : \
6278 : : if (shouldFree) \
6279 : : pfree(format); \
6280 : : \
6281 : : /* \
6282 : : * Insert the varlena header needed to make result a valid TEXT datum. \
6283 : : * The result is usually much bigger than it needs to be, but there \
6284 : : * seems little point in realloc'ing it smaller. \
6285 : : */ \
6286 : : SET_VARSIZE(result.data, result.len); \
6287 : : } while (0)
6288 : :
6289 : : /*
6290 : : * NUMERIC to_number() (convert string to numeric)
6291 : : */
6292 : : Datum
9553 bruce@momjian.us 6293 :CBC 16182 : numeric_to_number(PG_FUNCTION_ARGS)
6294 : : {
3455 noah@leadboat.com 6295 : 16182 : text *value = PG_GETARG_TEXT_PP(0);
6296 : 16182 : text *fmt = PG_GETARG_TEXT_PP(1);
6297 : : NUMDesc Num;
6298 : : Datum result;
6299 : : FormatNode *format;
6300 : : bool shouldFree;
6301 : : StringInfoData numstr;
6302 : : int len;
6303 : : int scale,
6304 : : precision;
6305 : :
6306 : 16182 : len = VARSIZE_ANY_EXHDR(fmt);
6307 : :
7 tgl@sss.pgh.pa.us 6308 [ - + ]:GNC 16182 : if (len <= 0)
7 tgl@sss.pgh.pa.us 6309 :UNC 0 : PG_RETURN_NULL(); /* arbitrary choice for empty format */
6310 : :
4367 bruce@momjian.us 6311 :CBC 16182 : format = NUM_cache(len, &Num, fmt, &shouldFree);
6312 : :
7 tgl@sss.pgh.pa.us 6313 :GNC 16174 : initStringInfo(&numstr);
6314 : :
6315 : 32348 : NUM_processor_from_char(format, &Num,
6316 : 16174 : VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value),
6317 : : &numstr,
6318 : : PG_GET_COLLATION());
6319 : :
4367 bruce@momjian.us 6320 :CBC 16118 : scale = Num.post;
3979 6321 : 16118 : precision = Num.pre + Num.multi + scale;
6322 : :
8742 6323 [ - + ]: 16118 : if (shouldFree)
9660 bruce@momjian.us 6324 :UBC 0 : pfree(format);
6325 : :
9553 bruce@momjian.us 6326 :CBC 16118 : result = DirectFunctionCall3(numeric_in,
6327 : : CStringGetDatum(numstr.data),
6328 : : ObjectIdGetDatum(InvalidOid),
6329 : : Int32GetDatum(((precision << 16) | scale) + VARHDRSZ));
6330 : :
3979 6331 [ + + ]: 16114 : if (IS_MULTI(&Num))
6332 : : {
6333 : : Numeric x;
2178 peter@eisentraut.org 6334 : 4 : Numeric a = int64_to_numeric(10);
6335 : 4 : Numeric b = int64_to_numeric(-Num.multi);
6336 : :
3979 bruce@momjian.us 6337 : 4 : x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
6338 : : NumericGetDatum(a),
6339 : : NumericGetDatum(b)));
6340 : 4 : result = DirectFunctionCall2(numeric_mul,
6341 : : result,
6342 : : NumericGetDatum(x));
6343 : : }
6344 : :
7 tgl@sss.pgh.pa.us 6345 :GNC 16114 : pfree(numstr.data);
9660 bruce@momjian.us 6346 :CBC 16114 : return result;
6347 : : }
6348 : :
6349 : : /*
6350 : : * NUMERIC to_char()
6351 : : */
6352 : : Datum
9553 6353 : 1201 : numeric_to_char(PG_FUNCTION_ARGS)
6354 : : {
9289 6355 : 1201 : Numeric value = PG_GETARG_NUMERIC(0);
3455 noah@leadboat.com 6356 : 1201 : text *fmt = PG_GETARG_TEXT_PP(1);
6357 : : NUMDesc Num;
6358 : : FormatNode *format;
6359 : : StringInfoData result;
6360 : : bool shouldFree;
4374 bruce@momjian.us 6361 : 1201 : int out_pre_spaces = 0,
9289 6362 : 1201 : sign = 0;
6363 : : char *numstr,
6364 : : *orgnum,
6365 : : *p;
6366 : :
9711 6367 [ - + - + ]: 1201 : NUM_TOCHAR_prepare;
6368 : :
6369 : : /*
6370 : : * On DateType depend part (numeric)
6371 : : */
4367 6372 [ + + ]: 1201 : if (IS_ROMAN(&Num))
6373 : : {
6374 : : int32 intvalue;
356 michael@paquier.xyz 6375 : 52 : ErrorSaveContext escontext = {T_ErrorSaveContext};
6376 : :
6377 : : /* Round and convert to int */
6378 : 52 : intvalue = numeric_int4_safe(value, (Node *) &escontext);
6379 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6380 [ + + ]: 52 : if (escontext.error_occurred)
700 tgl@sss.pgh.pa.us 6381 : 4 : intvalue = PG_INT32_MAX;
6382 : 52 : numstr = int_to_roman(intvalue);
6383 : : }
4367 bruce@momjian.us 6384 [ + + ]: 1149 : else if (IS_EEEE(&Num))
6385 : : {
6386 : 156 : orgnum = numeric_out_sci(value, Num.post);
6387 : :
6388 : : /*
6389 : : * numeric_out_sci() does not emit a sign for positive numbers. We
6390 : : * need to add a space in this case so that positive and negative
6391 : : * numbers are aligned. Also must check for NaN/infinity cases, which
6392 : : * we handle the same way as in float8_to_char.
6393 : : */
2227 tgl@sss.pgh.pa.us 6394 [ + + ]: 156 : if (strcmp(orgnum, "NaN") == 0 ||
6395 [ + + ]: 152 : strcmp(orgnum, "Infinity") == 0 ||
6396 [ + + ]: 148 : strcmp(orgnum, "-Infinity") == 0)
6397 : : {
6398 : : /*
6399 : : * Allow 6 characters for the leading sign, the decimal point,
6400 : : * "e", the exponent's sign and two exponent digits.
6401 : : */
4367 bruce@momjian.us 6402 : 12 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6403 : 12 : fill_str(numstr, '#', Num.pre + Num.post + 6);
6226 tgl@sss.pgh.pa.us 6404 : 12 : *numstr = ' ';
4367 bruce@momjian.us 6405 : 12 : *(numstr + Num.pre + 1) = '.';
6406 : : }
6226 tgl@sss.pgh.pa.us 6407 [ + + ]: 144 : else if (*orgnum != '-')
6408 : : {
6409 : 128 : numstr = (char *) palloc(strlen(orgnum) + 2);
6410 : 128 : *numstr = ' ';
6411 : 128 : strcpy(numstr + 1, orgnum);
6412 : : }
6413 : : else
6414 : : {
6415 : 16 : numstr = orgnum;
6416 : : }
6417 : : }
6418 : : else
6419 : : {
6420 : : size_t numstr_pre_len;
9633 bruce@momjian.us 6421 : 993 : Numeric val = value;
6422 : : Numeric x;
6423 : :
4367 6424 [ + + ]: 993 : if (IS_MULTI(&Num))
6425 : : {
2178 peter@eisentraut.org 6426 : 4 : Numeric a = int64_to_numeric(10);
6427 : 4 : Numeric b = int64_to_numeric(Num.multi);
6428 : :
9525 tgl@sss.pgh.pa.us 6429 : 4 : x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
6430 : : NumericGetDatum(a),
6431 : : NumericGetDatum(b)));
6432 : 4 : val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
6433 : : NumericGetDatum(value),
6434 : : NumericGetDatum(x)));
4367 bruce@momjian.us 6435 : 4 : Num.pre += Num.multi;
6436 : : }
6437 : :
9553 6438 : 993 : x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
6439 : : NumericGetDatum(val),
6440 : : Int32GetDatum(Num.post)));
6441 : 993 : orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
6442 : : NumericGetDatum(x)));
6443 : :
9633 6444 [ + + ]: 993 : if (*orgnum == '-')
6445 : : {
9711 6446 : 281 : sign = '-';
9633 6447 : 281 : numstr = orgnum + 1;
6448 : : }
6449 : : else
6450 : : {
9711 6451 : 712 : sign = '+';
6452 : 712 : numstr = orgnum;
6453 : : }
6454 : :
6455 [ + + ]: 993 : if ((p = strchr(numstr, '.')))
4374 6456 : 797 : numstr_pre_len = p - numstr;
6457 : : else
6458 : 196 : numstr_pre_len = strlen(numstr);
6459 : :
6460 : : /* needs padding? */
4367 6461 [ + + ]: 993 : if (numstr_pre_len < Num.pre)
6462 : 920 : out_pre_spaces = Num.pre - numstr_pre_len;
6463 : : /* overflowed prefix digit format? */
6464 [ + + ]: 73 : else if (numstr_pre_len > Num.pre)
6465 : : {
6466 : 20 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6467 : 20 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6468 : 20 : *(numstr + Num.pre) = '.';
6469 : : }
6470 : : }
6471 : :
9711 6472 [ + + ]: 1201 : NUM_TOCHAR_finish;
7 tgl@sss.pgh.pa.us 6473 :GNC 1201 : PG_RETURN_TEXT_P((text *) result.data);
6474 : : }
6475 : :
6476 : : /*
6477 : : * INT4 to_char()
6478 : : */
6479 : : Datum
9553 bruce@momjian.us 6480 :CBC 681477 : int4_to_char(PG_FUNCTION_ARGS)
6481 : : {
9289 6482 : 681477 : int32 value = PG_GETARG_INT32(0);
3455 noah@leadboat.com 6483 : 681477 : text *fmt = PG_GETARG_TEXT_PP(1);
6484 : : NUMDesc Num;
6485 : : FormatNode *format;
6486 : : StringInfoData result;
6487 : : bool shouldFree;
4374 bruce@momjian.us 6488 : 681477 : int out_pre_spaces = 0,
9289 6489 : 681477 : sign = 0;
6490 : : char *numstr,
6491 : : *orgnum;
6492 : :
9711 6493 [ - + - + ]: 681477 : NUM_TOCHAR_prepare;
6494 : :
6495 : : /*
6496 : : * On DateType depend part (int32)
6497 : : */
4367 6498 [ + + ]: 681477 : if (IS_ROMAN(&Num))
2182 tgl@sss.pgh.pa.us 6499 : 15996 : numstr = int_to_roman(value);
4367 bruce@momjian.us 6500 [ + + ]: 665481 : else if (IS_EEEE(&Num))
6501 : : {
6502 : : /* we can do it easily because float8 won't lose any precision */
6026 6503 : 4 : float8 val = (float8) value;
6504 : :
3087 peter_e@gmx.net 6505 : 4 : orgnum = (char *) psprintf("%+.*e", Num.post, val);
6506 : :
6507 : : /*
6508 : : * Swap a leading positive sign for a space.
6509 : : */
6226 tgl@sss.pgh.pa.us 6510 [ + - ]: 4 : if (*orgnum == '+')
6511 : 4 : *orgnum = ' ';
6512 : :
6513 : 4 : numstr = orgnum;
6514 : : }
6515 : : else
6516 : : {
6517 : : size_t numstr_pre_len;
6518 : :
4367 bruce@momjian.us 6519 [ + + ]: 665477 : if (IS_MULTI(&Num))
6520 : : {
9579 tgl@sss.pgh.pa.us 6521 : 4 : orgnum = DatumGetCString(DirectFunctionCall1(int4out,
6522 : : Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi)))));
4367 bruce@momjian.us 6523 : 4 : Num.pre += Num.multi;
6524 : : }
6525 : : else
6526 : : {
9579 tgl@sss.pgh.pa.us 6527 : 665473 : orgnum = DatumGetCString(DirectFunctionCall1(int4out,
6528 : : Int32GetDatum(value)));
6529 : : }
6530 : :
9633 bruce@momjian.us 6531 [ - + ]: 665477 : if (*orgnum == '-')
6532 : : {
9711 bruce@momjian.us 6533 :UBC 0 : sign = '-';
6999 tgl@sss.pgh.pa.us 6534 : 0 : orgnum++;
6535 : : }
6536 : : else
9711 bruce@momjian.us 6537 :CBC 665477 : sign = '+';
6538 : :
4374 6539 : 665477 : numstr_pre_len = strlen(orgnum);
6540 : :
6541 : : /* post-decimal digits? Pad out with zeros. */
4367 6542 [ - + ]: 665477 : if (Num.post)
6543 : : {
4367 bruce@momjian.us 6544 :UBC 0 : numstr = (char *) palloc(numstr_pre_len + Num.post + 2);
6999 tgl@sss.pgh.pa.us 6545 : 0 : strcpy(numstr, orgnum);
4374 bruce@momjian.us 6546 : 0 : *(numstr + numstr_pre_len) = '.';
4367 6547 : 0 : memset(numstr + numstr_pre_len + 1, '0', Num.post);
6548 : 0 : *(numstr + numstr_pre_len + Num.post + 1) = '\0';
6549 : : }
6550 : : else
6999 tgl@sss.pgh.pa.us 6551 :CBC 665477 : numstr = orgnum;
6552 : :
6553 : : /* needs padding? */
4367 bruce@momjian.us 6554 [ + + ]: 665477 : if (numstr_pre_len < Num.pre)
6555 : 655941 : out_pre_spaces = Num.pre - numstr_pre_len;
6556 : : /* overflowed prefix digit format? */
6557 [ - + ]: 9536 : else if (numstr_pre_len > Num.pre)
6558 : : {
4367 bruce@momjian.us 6559 :UBC 0 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6560 : 0 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6561 : 0 : *(numstr + Num.pre) = '.';
6562 : : }
6563 : : }
6564 : :
9711 bruce@momjian.us 6565 [ - + ]:CBC 681477 : NUM_TOCHAR_finish;
7 tgl@sss.pgh.pa.us 6566 :GNC 681477 : PG_RETURN_TEXT_P((text *) result.data);
6567 : : }
6568 : :
6569 : : /*
6570 : : * INT8 to_char()
6571 : : */
6572 : : Datum
9553 bruce@momjian.us 6573 :CBC 473 : int8_to_char(PG_FUNCTION_ARGS)
6574 : : {
9289 6575 : 473 : int64 value = PG_GETARG_INT64(0);
3455 noah@leadboat.com 6576 : 473 : text *fmt = PG_GETARG_TEXT_PP(1);
6577 : : NUMDesc Num;
6578 : : FormatNode *format;
6579 : : StringInfoData result;
6580 : : bool shouldFree;
4374 bruce@momjian.us 6581 : 473 : int out_pre_spaces = 0,
9289 6582 : 473 : sign = 0;
6583 : : char *numstr,
6584 : : *orgnum;
6585 : :
9711 6586 [ - + - + ]: 473 : NUM_TOCHAR_prepare;
6587 : :
6588 : : /*
6589 : : * On DateType depend part (int64)
6590 : : */
4367 6591 [ + + ]: 473 : if (IS_ROMAN(&Num))
6592 : : {
6593 : : int32 intvalue;
6594 : :
6595 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
700 tgl@sss.pgh.pa.us 6596 [ + + + + ]: 20 : if (value <= PG_INT32_MAX && value >= PG_INT32_MIN)
6597 : 8 : intvalue = (int32) value;
6598 : : else
6599 : 12 : intvalue = PG_INT32_MAX;
6600 : 20 : numstr = int_to_roman(intvalue);
6601 : : }
4367 bruce@momjian.us 6602 [ + + ]: 453 : else if (IS_EEEE(&Num))
6603 : : {
6604 : : /* to avoid loss of precision, must go via numeric not float8 */
2178 peter@eisentraut.org 6605 : 8 : orgnum = numeric_out_sci(int64_to_numeric(value),
6606 : : Num.post);
6607 : :
6608 : : /*
6609 : : * numeric_out_sci() does not emit a sign for positive numbers. We
6610 : : * need to add a space in this case so that positive and negative
6611 : : * numbers are aligned. We don't have to worry about NaN/inf here.
6612 : : */
6226 tgl@sss.pgh.pa.us 6613 [ + + ]: 8 : if (*orgnum != '-')
6614 : : {
6615 : 4 : numstr = (char *) palloc(strlen(orgnum) + 2);
6616 : 4 : *numstr = ' ';
6617 : 4 : strcpy(numstr + 1, orgnum);
6618 : : }
6619 : : else
6620 : : {
6621 : 4 : numstr = orgnum;
6622 : : }
6623 : : }
6624 : : else
6625 : : {
6626 : : size_t numstr_pre_len;
6627 : :
4367 bruce@momjian.us 6628 [ + + ]: 445 : if (IS_MULTI(&Num))
6629 : : {
6630 : 4 : double multi = pow((double) 10, (double) Num.multi);
6631 : :
9553 6632 : 4 : value = DatumGetInt64(DirectFunctionCall2(int8mul,
6633 : : Int64GetDatum(value),
6634 : : DirectFunctionCall1(dtoi8,
6635 : : Float8GetDatum(multi))));
4367 6636 : 4 : Num.pre += Num.multi;
6637 : : }
6638 : :
9553 6639 : 445 : orgnum = DatumGetCString(DirectFunctionCall1(int8out,
6640 : : Int64GetDatum(value)));
6641 : :
9633 6642 [ + + ]: 445 : if (*orgnum == '-')
6643 : : {
9711 6644 : 136 : sign = '-';
6999 tgl@sss.pgh.pa.us 6645 : 136 : orgnum++;
6646 : : }
6647 : : else
9711 bruce@momjian.us 6648 : 309 : sign = '+';
6649 : :
4374 6650 : 445 : numstr_pre_len = strlen(orgnum);
6651 : :
6652 : : /* post-decimal digits? Pad out with zeros. */
4367 6653 [ + + ]: 445 : if (Num.post)
6654 : : {
6655 : 140 : numstr = (char *) palloc(numstr_pre_len + Num.post + 2);
6999 tgl@sss.pgh.pa.us 6656 : 140 : strcpy(numstr, orgnum);
4374 bruce@momjian.us 6657 : 140 : *(numstr + numstr_pre_len) = '.';
4367 6658 : 140 : memset(numstr + numstr_pre_len + 1, '0', Num.post);
6659 : 140 : *(numstr + numstr_pre_len + Num.post + 1) = '\0';
6660 : : }
6661 : : else
6999 tgl@sss.pgh.pa.us 6662 : 305 : numstr = orgnum;
6663 : :
6664 : : /* needs padding? */
4367 bruce@momjian.us 6665 [ + + ]: 445 : if (numstr_pre_len < Num.pre)
6666 : 180 : out_pre_spaces = Num.pre - numstr_pre_len;
6667 : : /* overflowed prefix digit format? */
6668 [ - + ]: 265 : else if (numstr_pre_len > Num.pre)
6669 : : {
4367 bruce@momjian.us 6670 :UBC 0 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6671 : 0 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6672 : 0 : *(numstr + Num.pre) = '.';
6673 : : }
6674 : : }
6675 : :
9711 bruce@momjian.us 6676 [ + + ]:CBC 473 : NUM_TOCHAR_finish;
7 tgl@sss.pgh.pa.us 6677 :GNC 473 : PG_RETURN_TEXT_P((text *) result.data);
6678 : : }
6679 : :
6680 : : /*
6681 : : * FLOAT4 to_char()
6682 : : */
6683 : : Datum
9553 bruce@momjian.us 6684 :CBC 98 : float4_to_char(PG_FUNCTION_ARGS)
6685 : : {
9289 6686 : 98 : float4 value = PG_GETARG_FLOAT4(0);
3455 noah@leadboat.com 6687 : 98 : text *fmt = PG_GETARG_TEXT_PP(1);
6688 : : NUMDesc Num;
6689 : : FormatNode *format;
6690 : : StringInfoData result;
6691 : : bool shouldFree;
4374 bruce@momjian.us 6692 : 98 : int out_pre_spaces = 0,
9289 6693 : 98 : sign = 0;
6694 : : char *numstr,
6695 : : *p;
6696 : :
9711 6697 [ - + - + ]: 98 : NUM_TOCHAR_prepare;
6698 : :
4367 6699 [ + + ]: 98 : if (IS_ROMAN(&Num))
6700 : : {
6701 : : int32 intvalue;
6702 : :
6703 : : /* See notes in ftoi4() */
700 tgl@sss.pgh.pa.us 6704 : 8 : value = rint(value);
6705 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6706 [ + - + - : 8 : if (!isnan(value) && FLOAT4_FITS_IN_INT32(value))
+ + ]
6707 : 4 : intvalue = (int32) value;
6708 : : else
6709 : 4 : intvalue = PG_INT32_MAX;
6710 : 8 : numstr = int_to_roman(intvalue);
6711 : : }
4367 bruce@momjian.us 6712 [ + + ]: 90 : else if (IS_EEEE(&Num))
6713 : : {
2882 tgl@sss.pgh.pa.us 6714 [ + + + + ]: 28 : if (isnan(value) || isinf(value))
6715 : : {
6716 : : /*
6717 : : * Allow 6 characters for the leading sign, the decimal point,
6718 : : * "e", the exponent's sign and two exponent digits.
6719 : : */
4367 bruce@momjian.us 6720 : 12 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6721 : 12 : fill_str(numstr, '#', Num.pre + Num.post + 6);
6226 tgl@sss.pgh.pa.us 6722 : 12 : *numstr = ' ';
4367 bruce@momjian.us 6723 : 12 : *(numstr + Num.pre + 1) = '.';
6724 : : }
6725 : : else
6726 : : {
2183 tgl@sss.pgh.pa.us 6727 : 16 : numstr = psprintf("%+.*e", Num.post, value);
6728 : :
6729 : : /*
6730 : : * Swap a leading positive sign for a space.
6731 : : */
6732 [ + + ]: 16 : if (*numstr == '+')
6733 : 12 : *numstr = ' ';
6734 : : }
6735 : : }
6736 : : else
6737 : : {
9553 bruce@momjian.us 6738 : 62 : float4 val = value;
6739 : : char *orgnum;
6740 : : size_t numstr_pre_len;
6741 : :
4367 6742 [ + + ]: 62 : if (IS_MULTI(&Num))
6743 : : {
6744 : 4 : float multi = pow((double) 10, (double) Num.multi);
6745 : :
9553 6746 : 4 : val = value * multi;
4367 6747 : 4 : Num.pre += Num.multi;
6748 : : }
6749 : :
2183 tgl@sss.pgh.pa.us 6750 : 62 : orgnum = psprintf("%.0f", fabs(val));
4176 bruce@momjian.us 6751 : 62 : numstr_pre_len = strlen(orgnum);
6752 : :
6753 : : /* adjust post digits to fit max float digits */
6754 [ + + ]: 62 : if (numstr_pre_len >= FLT_DIG)
6755 : 28 : Num.post = 0;
6756 [ - + ]: 34 : else if (numstr_pre_len + Num.post > FLT_DIG)
4176 bruce@momjian.us 6757 :UBC 0 : Num.post = FLT_DIG - numstr_pre_len;
3087 peter_e@gmx.net 6758 :CBC 62 : orgnum = psprintf("%.*f", Num.post, val);
6759 : :
9633 bruce@momjian.us 6760 [ + + ]: 62 : if (*orgnum == '-')
6761 : : { /* < 0 */
9711 6762 : 16 : sign = '-';
9633 6763 : 16 : numstr = orgnum + 1;
6764 : : }
6765 : : else
6766 : : {
9711 6767 : 46 : sign = '+';
6768 : 46 : numstr = orgnum;
6769 : : }
6770 : :
6771 [ + + ]: 62 : if ((p = strchr(numstr, '.')))
4374 6772 : 26 : numstr_pre_len = p - numstr;
6773 : : else
6774 : 36 : numstr_pre_len = strlen(numstr);
6775 : :
6776 : : /* needs padding? */
4367 6777 [ + + ]: 62 : if (numstr_pre_len < Num.pre)
6778 : 40 : out_pre_spaces = Num.pre - numstr_pre_len;
6779 : : /* overflowed prefix digit format? */
6780 [ + + ]: 22 : else if (numstr_pre_len > Num.pre)
6781 : : {
6782 : 16 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6783 : 16 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6784 : 16 : *(numstr + Num.pre) = '.';
6785 : : }
6786 : : }
6787 : :
9711 6788 [ - + ]: 98 : NUM_TOCHAR_finish;
7 tgl@sss.pgh.pa.us 6789 :GNC 98 : PG_RETURN_TEXT_P((text *) result.data);
6790 : : }
6791 : :
6792 : : /*
6793 : : * FLOAT8 to_char()
6794 : : */
6795 : : Datum
9553 bruce@momjian.us 6796 :CBC 113 : float8_to_char(PG_FUNCTION_ARGS)
6797 : : {
9289 6798 : 113 : float8 value = PG_GETARG_FLOAT8(0);
3455 noah@leadboat.com 6799 : 113 : text *fmt = PG_GETARG_TEXT_PP(1);
6800 : : NUMDesc Num;
6801 : : FormatNode *format;
6802 : : StringInfoData result;
6803 : : bool shouldFree;
4374 bruce@momjian.us 6804 : 113 : int out_pre_spaces = 0,
9289 6805 : 113 : sign = 0;
6806 : : char *numstr,
6807 : : *p;
6808 : :
9711 6809 [ - + - + ]: 113 : NUM_TOCHAR_prepare;
6810 : :
4367 6811 [ + + ]: 113 : if (IS_ROMAN(&Num))
6812 : : {
6813 : : int32 intvalue;
6814 : :
6815 : : /* See notes in dtoi4() */
700 tgl@sss.pgh.pa.us 6816 : 12 : value = rint(value);
6817 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6818 [ + - + - : 12 : if (!isnan(value) && FLOAT8_FITS_IN_INT32(value))
+ + ]
6819 : 8 : intvalue = (int32) value;
6820 : : else
6821 : 4 : intvalue = PG_INT32_MAX;
6822 : 12 : numstr = int_to_roman(intvalue);
6823 : : }
4367 bruce@momjian.us 6824 [ + + ]: 101 : else if (IS_EEEE(&Num))
6825 : : {
2882 tgl@sss.pgh.pa.us 6826 [ + + + + ]: 28 : if (isnan(value) || isinf(value))
6827 : : {
6828 : : /*
6829 : : * Allow 6 characters for the leading sign, the decimal point,
6830 : : * "e", the exponent's sign and two exponent digits.
6831 : : */
4367 bruce@momjian.us 6832 : 12 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6833 : 12 : fill_str(numstr, '#', Num.pre + Num.post + 6);
6226 tgl@sss.pgh.pa.us 6834 : 12 : *numstr = ' ';
4367 bruce@momjian.us 6835 : 12 : *(numstr + Num.pre + 1) = '.';
6836 : : }
6837 : : else
6838 : : {
2183 tgl@sss.pgh.pa.us 6839 : 16 : numstr = psprintf("%+.*e", Num.post, value);
6840 : :
6841 : : /*
6842 : : * Swap a leading positive sign for a space.
6843 : : */
6844 [ + + ]: 16 : if (*numstr == '+')
6845 : 12 : *numstr = ' ';
6846 : : }
6847 : : }
6848 : : else
6849 : : {
9553 bruce@momjian.us 6850 : 73 : float8 val = value;
6851 : : char *orgnum;
6852 : : size_t numstr_pre_len;
6853 : :
4367 6854 [ + + ]: 73 : if (IS_MULTI(&Num))
6855 : : {
6856 : 4 : double multi = pow((double) 10, (double) Num.multi);
6857 : :
9553 6858 : 4 : val = value * multi;
4367 6859 : 4 : Num.pre += Num.multi;
6860 : : }
6861 : :
3087 peter_e@gmx.net 6862 : 73 : orgnum = psprintf("%.0f", fabs(val));
6863 : 73 : numstr_pre_len = strlen(orgnum);
6864 : :
6865 : : /* adjust post digits to fit max double digits */
4176 bruce@momjian.us 6866 [ + + ]: 73 : if (numstr_pre_len >= DBL_DIG)
6867 : 4 : Num.post = 0;
6868 [ + + ]: 69 : else if (numstr_pre_len + Num.post > DBL_DIG)
6869 : 4 : Num.post = DBL_DIG - numstr_pre_len;
3087 peter_e@gmx.net 6870 : 73 : orgnum = psprintf("%.*f", Num.post, val);
6871 : :
9633 bruce@momjian.us 6872 [ + + ]: 73 : if (*orgnum == '-')
6873 : : { /* < 0 */
9711 6874 : 16 : sign = '-';
9633 6875 : 16 : numstr = orgnum + 1;
6876 : : }
6877 : : else
6878 : : {
9711 6879 : 57 : sign = '+';
6880 : 57 : numstr = orgnum;
6881 : : }
6882 : :
6883 [ + + ]: 73 : if ((p = strchr(numstr, '.')))
4374 6884 : 41 : numstr_pre_len = p - numstr;
6885 : : else
6886 : 32 : numstr_pre_len = strlen(numstr);
6887 : :
6888 : : /* needs padding? */
4367 6889 [ + + ]: 73 : if (numstr_pre_len < Num.pre)
6890 : 44 : out_pre_spaces = Num.pre - numstr_pre_len;
6891 : : /* overflowed prefix digit format? */
6892 [ + + ]: 29 : else if (numstr_pre_len > Num.pre)
6893 : : {
6894 : 20 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6895 : 20 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6896 : 20 : *(numstr + Num.pre) = '.';
6897 : : }
6898 : : }
6899 : :
9711 6900 [ - + ]: 113 : NUM_TOCHAR_finish;
7 tgl@sss.pgh.pa.us 6901 :GNC 113 : PG_RETURN_TEXT_P((text *) result.data);
6902 : : }
|