Skip to content

Commit 0a87ddf

Browse files
committed
Cache the result of converting now() to a struct pg_tm.
SQL operations such as CURRENT_DATE, CURRENT_TIME, LOCALTIME, and conversion of "now" in a datetime input string have to obtain the transaction start timestamp ("now()") as a broken-down struct pg_tm. This is a remarkably expensive conversion, and since now() does not change intra-transaction, it doesn't really need to be done more than once per transaction. Introducing a simple cache provides visible speedups in queries that compute these values many times, for example insertion of many rows that use a default value of CURRENT_DATE. Peter Smith, with a bit of kibitzing by me Discussion: https://postgr.es/m/CAHut+Pu89TWjq530V2gY5O6SWi=OEJMQ_VHMt8bdZB_9JFna5A@mail.gmail.com
1 parent e21cbb4 commit 0a87ddf

File tree

2 files changed

+80
-36
lines changed

2 files changed

+80
-36
lines changed

src/backend/utils/adt/date.c

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -299,20 +299,31 @@ EncodeSpecialDate(DateADT dt, char *str)
299299
DateADT
300300
GetSQLCurrentDate(void)
301301
{
302-
TimestampTz ts;
303-
struct pg_tm tt,
304-
*tm = &tt;
305-
fsec_t fsec;
306-
int tz;
302+
struct pg_tm tm;
307303

308-
ts = GetCurrentTransactionStartTimestamp();
304+
static int cache_year = 0;
305+
static int cache_mon = 0;
306+
static int cache_mday = 0;
307+
static DateADT cache_date;
309308

310-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
311-
ereport(ERROR,
312-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
313-
errmsg("timestamp out of range")));
309+
GetCurrentDateTime(&tm);
314310

315-
return date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
311+
/*
312+
* date2j involves several integer divisions; moreover, unless our session
313+
* lives across local midnight, we don't really have to do it more than
314+
* once. So it seems worth having a separate cache here.
315+
*/
316+
if (tm.tm_year != cache_year ||
317+
tm.tm_mon != cache_mon ||
318+
tm.tm_mday != cache_mday)
319+
{
320+
cache_date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
321+
cache_year = tm.tm_year;
322+
cache_mon = tm.tm_mon;
323+
cache_mday = tm.tm_mday;
324+
}
325+
326+
return cache_date;
316327
}
317328

318329
/*
@@ -322,18 +333,12 @@ TimeTzADT *
322333
GetSQLCurrentTime(int32 typmod)
323334
{
324335
TimeTzADT *result;
325-
TimestampTz ts;
326336
struct pg_tm tt,
327337
*tm = &tt;
328338
fsec_t fsec;
329339
int tz;
330340

331-
ts = GetCurrentTransactionStartTimestamp();
332-
333-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
334-
ereport(ERROR,
335-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
336-
errmsg("timestamp out of range")));
341+
GetCurrentTimeUsec(tm, &fsec, &tz);
337342

338343
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
339344
tm2timetz(tm, fsec, tz, result);
@@ -348,18 +353,12 @@ TimeADT
348353
GetSQLLocalTime(int32 typmod)
349354
{
350355
TimeADT result;
351-
TimestampTz ts;
352356
struct pg_tm tt,
353357
*tm = &tt;
354358
fsec_t fsec;
355359
int tz;
356360

357-
ts = GetCurrentTransactionStartTimestamp();
358-
359-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
360-
ereport(ERROR,
361-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
362-
errmsg("timestamp out of range")));
361+
GetCurrentTimeUsec(tm, &fsec, &tz);
363362

364363
tm2time(tm, fsec, &result);
365364
AdjustTimeForTypmod(&result, typmod);

src/backend/utils/adt/datetime.c

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,35 +339,80 @@ j2day(int date)
339339
/*
340340
* GetCurrentDateTime()
341341
*
342-
* Get the transaction start time ("now()") broken down as a struct pg_tm.
342+
* Get the transaction start time ("now()") broken down as a struct pg_tm,
343+
* converted according to the session timezone setting.
344+
*
345+
* This is just a convenience wrapper for GetCurrentTimeUsec, to cover the
346+
* case where caller doesn't need either fractional seconds or tz offset.
343347
*/
344348
void
345349
GetCurrentDateTime(struct pg_tm *tm)
346350
{
347-
int tz;
348351
fsec_t fsec;
349352

350-
timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, &fsec,
351-
NULL, NULL);
352-
/* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
353+
GetCurrentTimeUsec(tm, &fsec, NULL);
353354
}
354355

355356
/*
356357
* GetCurrentTimeUsec()
357358
*
358359
* Get the transaction start time ("now()") broken down as a struct pg_tm,
359-
* including fractional seconds and timezone offset.
360+
* including fractional seconds and timezone offset. The time is converted
361+
* according to the session timezone setting.
362+
*
363+
* Callers may pass tzp = NULL if they don't need the offset, but this does
364+
* not affect the conversion behavior (unlike timestamp2tm()).
365+
*
366+
* Internally, we cache the result, since this could be called many times
367+
* in a transaction, within which now() doesn't change.
360368
*/
361369
void
362370
GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
363371
{
364-
int tz;
372+
TimestampTz cur_ts = GetCurrentTransactionStartTimestamp();
373+
374+
/*
375+
* The cache key must include both current time and current timezone. By
376+
* representing the timezone by just a pointer, we're assuming that
377+
* distinct timezone settings could never have the same pointer value.
378+
* This is true by virtue of the hashtable used inside pg_tzset();
379+
* however, it might need another look if we ever allow entries in that
380+
* hash to be recycled.
381+
*/
382+
static TimestampTz cache_ts = 0;
383+
static pg_tz *cache_timezone = NULL;
384+
static struct pg_tm cache_tm;
385+
static fsec_t cache_fsec;
386+
static int cache_tz;
387+
388+
if (cur_ts != cache_ts || session_timezone != cache_timezone)
389+
{
390+
/*
391+
* Make sure cache is marked invalid in case of error after partial
392+
* update within timestamp2tm.
393+
*/
394+
cache_timezone = NULL;
395+
396+
/*
397+
* Perform the computation, storing results into cache. We do not
398+
* really expect any error here, since current time surely ought to be
399+
* within range, but check just for sanity's sake.
400+
*/
401+
if (timestamp2tm(cur_ts, &cache_tz, &cache_tm, &cache_fsec,
402+
NULL, session_timezone) != 0)
403+
ereport(ERROR,
404+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
405+
errmsg("timestamp out of range")));
406+
407+
/* OK, so mark the cache valid. */
408+
cache_ts = cur_ts;
409+
cache_timezone = session_timezone;
410+
}
365411

366-
timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, fsec,
367-
NULL, NULL);
368-
/* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
412+
*tm = cache_tm;
413+
*fsec = cache_fsec;
369414
if (tzp != NULL)
370-
*tzp = tz;
415+
*tzp = cache_tz;
371416
}
372417

373418

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy