Index: src/backend/catalog/system_views.sql =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/catalog/system_views.sql,v retrieving revision 1.30 diff -c -r1.30 system_views.sql *** src/backend/catalog/system_views.sql 19 Aug 2006 01:36:24 -0000 1.30 --- src/backend/catalog/system_views.sql 7 Sep 2006 12:27:50 -0000 *************** *** 186,196 **** GRANT SELECT, UPDATE ON pg_settings TO PUBLIC; ! CREATE VIEW pg_timezonenames AS SELECT * ! FROM pg_timezonenames() AS T (name text, utc_offset interval, is_dst boolean); -- Statistics views CREATE VIEW pg_stat_all_tables AS --- 186,201 ---- GRANT SELECT, UPDATE ON pg_settings TO PUBLIC; ! CREATE VIEW pg_timezone_abbrevs AS SELECT * ! FROM pg_timezone_abbrevs() AS T (name text, utc_offset interval, is_dst boolean); + CREATE VIEW pg_timezone_names AS + SELECT * + FROM pg_timezone_names() AS T + (name text); + -- Statistics views CREATE VIEW pg_stat_all_tables AS Index: src/backend/utils/adt/datetime.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/datetime.c,v retrieving revision 1.170 diff -c -r1.170 datetime.c *** src/backend/utils/adt/datetime.c 4 Sep 2006 01:26:27 -0000 1.170 --- src/backend/utils/adt/datetime.c 7 Sep 2006 12:27:53 -0000 *************** *** 3842,3848 **** * and returns a set of (name, utc_offset, is_dst). */ Datum ! pg_timezonenames(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int *pindex; --- 3842,3848 ---- * and returns a set of (name, utc_offset, is_dst). */ Datum ! pg_timezone_abbrevs(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int *pindex; *************** *** 3877,3883 **** /* * build tupdesc for result tuples. This must match the ! * definition of the pg_timezonenames view in system_views.sql */ tupdesc = CreateTemplateTupleDesc(3, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", --- 3877,3883 ---- /* * build tupdesc for result tuples. This must match the ! * definition of the pg_timezone_names view in system_views.sql */ tupdesc = CreateTemplateTupleDesc(3, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", *************** *** 3928,3930 **** --- 3928,3995 ---- SRF_RETURN_NEXT(funcctx, result); } + + /* + * This set-returning function reads all the available time zone names + */ + Datum + pg_timezone_names(PG_FUNCTION_ARGS) + { + MemoryContext oldcontext; + FuncCallContext *funcctx; + pg_tzenum *tzenum; + pg_tz *tz; + Datum result; + HeapTuple tuple; + Datum values[1]; + bool nulls[1]; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function + * calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* allocate memory for user context */ + tzenum = pg_tzenumerate_start(); + funcctx->user_fctx = (void *) tzenum; + + /* + * build tupdesc for result tuples. This must match the + * definition of the pg_timezone_abbrevs view in system_views.sql + */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + tzenum = (pg_tzenum *) funcctx->user_fctx; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + tz = pg_tzenumerate_next(tzenum); + MemoryContextSwitchTo(oldcontext); + if (!tz) + SRF_RETURN_DONE(funcctx); + + MemSet(nulls, 0, sizeof(nulls)); + + values[0] = DirectFunctionCall1(textin, CStringGetDatum(pg_get_timezone_name(tz))); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } Index: src/include/pgtime.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/pgtime.h,v retrieving revision 1.12 diff -c -r1.12 pgtime.h *** src/include/pgtime.h 5 Mar 2006 15:58:53 -0000 1.12 --- src/include/pgtime.h 7 Sep 2006 12:27:56 -0000 *************** *** 38,43 **** --- 38,44 ---- }; typedef struct pg_tz pg_tz; + typedef struct pg_tzenum pg_tzenum; extern struct pg_tm *pg_localtime(const pg_time_t *timep, const pg_tz *tz); extern struct pg_tm *pg_gmtime(const pg_time_t *timep); *************** *** 56,61 **** --- 57,66 ---- extern bool tz_acceptable(pg_tz *tz); extern const char *pg_get_timezone_name(pg_tz *tz); + extern pg_tzenum *pg_tzenumerate_start(void); + extern pg_tz *pg_tzenumerate_next(pg_tzenum *dir); + extern void pg_tzenumerate_end(pg_tzenum *dir); + extern pg_tz *global_timezone; /* Maximum length of a timezone name (not including trailing null) */ Index: src/include/catalog/pg_proc.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_proc.h,v retrieving revision 1.422 diff -c -r1.422 pg_proc.h *** src/include/catalog/pg_proc.h 19 Aug 2006 01:36:33 -0000 1.422 --- src/include/catalog/pg_proc.h 7 Sep 2006 12:27:58 -0000 *************** *** 3787,3793 **** DESCR("get the prepared statements for this session"); DATA(insert OID = 2511 ( pg_cursor PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_cursor - _null_ )); DESCR("get the open cursors for this session"); ! DATA(insert OID = 2599 ( pg_timezonenames PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_timezonenames - _null_ )); DESCR("get the available time zone names"); /* non-persistent series generator */ --- 3787,3795 ---- DESCR("get the prepared statements for this session"); DATA(insert OID = 2511 ( pg_cursor PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_cursor - _null_ )); DESCR("get the open cursors for this session"); ! DATA(insert OID = 2599 ( pg_timezone_abbrevs PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_timezone_abbrevs - _null_ )); ! DESCR("get the available time zone abbreviations"); ! DATA(insert OID = 2600 ( pg_timezone_names PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_timezone_names - _null_ )); DESCR("get the available time zone names"); /* non-persistent series generator */ Index: src/include/utils/datetime.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/utils/datetime.h,v retrieving revision 1.60 diff -c -r1.60 datetime.h *** src/include/utils/datetime.h 25 Jul 2006 03:51:22 -0000 1.60 --- src/include/utils/datetime.h 7 Sep 2006 12:27:59 -0000 *************** *** 303,308 **** extern bool CheckDateTokenTables(void); extern void InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n); ! extern Datum pg_timezonenames(PG_FUNCTION_ARGS); #endif /* DATETIME_H */ --- 303,309 ---- extern bool CheckDateTokenTables(void); extern void InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n); ! extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); ! extern Datum pg_timezone_names(PG_FUNCTION_ARGS); #endif /* DATETIME_H */ Index: src/timezone/pgtz.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/timezone/pgtz.c,v retrieving revision 1.44 diff -c -r1.44 pgtz.c *** src/timezone/pgtz.c 14 Jul 2006 14:52:27 -0000 1.44 --- src/timezone/pgtz.c 7 Sep 2006 12:28:04 -0000 *************** *** 1136,1138 **** --- 1136,1246 ---- SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ARGV); } } + + + /* + * Functions to enumerate available timezones + * + * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum structure, + * so the data is only valid up to the next call. + * + * All data is allocated using palloc in the current context. + */ + #define MAX_TZDIR_DEPTH 10 + struct pg_tzenum { + int baselen; + int depth; + DIR *dirdesc[MAX_TZDIR_DEPTH]; + char *dirname[MAX_TZDIR_DEPTH]; + struct pg_tz tz; + } ; + + pg_tzenum * + pg_tzenumerate_start(void) + { + struct pg_tzenum *ret = palloc(sizeof(struct pg_tzenum)); + + ret->baselen = strlen(pg_TZDIR())+1; + ret->depth = 0; + ret->dirname[0] = pstrdup(pg_TZDIR()); + ret->dirdesc[0] = AllocateDir(pg_TZDIR()); + if (!ret->dirdesc[0]) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", pg_TZDIR()))); + + return ret; + } + + void + pg_tzenumerate_end(pg_tzenum *dir) + { + while (dir->depth >= 0) + { + FreeDir(dir->dirdesc[dir->depth]); + pfree(dir->dirdesc[dir->depth]); + dir->depth--; + } + pfree(dir); + } + + pg_tz * + pg_tzenumerate_next(pg_tzenum *dir) + { + while (dir->depth >= 0) + { + struct dirent *direntry; + char fullname[MAXPGPATH]; + struct stat statbuf; + + direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]); + + if (!direntry) + { + /* End of this directory */ + FreeDir(dir->dirdesc[dir->depth]); + dir->depth--; + continue; + } + + if (direntry->d_name[0] == '.') + continue; + + snprintf(fullname, MAXPGPATH, "%s/%s", dir->dirname[dir->depth], direntry->d_name); + if (stat(fullname, &statbuf) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat \"%s\": %m", fullname))); + + if (S_ISDIR(statbuf.st_mode)) + { + /* Step into the subdirectory */ + if (dir->depth == MAX_TZDIR_DEPTH-1) + ereport(ERROR, + (errmsg("timezone directory stack overflow"))); + dir->depth++; + dir->dirname[dir->depth] = pstrdup(fullname); + dir->dirdesc[dir->depth] = AllocateDir(fullname); + if (!dir->dirdesc[dir->depth]) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", fullname))); + + /* Start over reading in the new directory */ + continue; + } + + /* Load this timezone using tzload() not pg_tzset(), so we don't fill the cache */ + if (tzload(fullname + dir->baselen, &dir->tz.state) != 0) + /* Zone could not be loaded, and we don't do parsing. Try next. */ + continue; + + /* Timezone loaded OK. */ + strcpy(dir->tz.TZname, fullname + dir->baselen); + return &dir->tz; + } + + /* Nothing more found */ + return NULL; + } +