#include "pg_bikram_sambat.h" PG_FUNCTION_INFO_V1(ad_to_bs); /* This function basically only: - accepts the argument - converts it to date values (only for validation) - performs validation (by calling adToBs itself) - returns it (the argument itself) - the actual conversion happens in the bs_date_out function because the SQL function that makes use of this function has a return type 'bs_date' So where does the conversion happen: - if this function is used in an INSERT statement, no conversion needs to happen since we are basically storing int32 AD dates for bs_date type - if this function is used in a SELECT statement, the bs_date_out conversion function will handle the conversion from AD to BS Why not make use of the bs_date_out function in the `CREATE FUNCTION ad_to_bs` statement then? - because the bs_date_out function returns a cstring/text which is: - fine for SELECT statements - but not correct for INSERT statements If the date validation happens in the bs_date_out function, why do it here in this ad_to_bs function as well? - because the bs_date_out function only gets called for SELECT statements, - for INSERT statements that may make use of this ad_to_bs function, validation will not happen otherwise */ Datum ad_to_bs(PG_FUNCTION_ARGS) { DateADT adDate = PG_GETARG_DATEADT(0); int year, month, day; j2date(adDate + POSTGRES_EPOCH_JDATE, &year, &month, &day); adToBs(year, month, day); // done in order to trigger all of the valdations PG_RETURN_INT32((int32)adDate); } PG_FUNCTION_INFO_V1(bs_date_to_char); Datum bs_date_to_char(PG_FUNCTION_ARGS) { BSDateADT date = PG_GETARG_INT32(0); text *fmtStrArg = PG_GETARG_TEXT_PP(1); char *fmtStrC = text_to_cstring(fmtStrArg); DateParts bsDate; char *formattedDate; text *returnText; int year, month, day; j2date(date + POSTGRES_EPOCH_JDATE, &year, &month, &day); bsDate = adToBs(year, month, day); formattedDate = extractDateWithFmtStr(&bsDate, fmtStrC, NULL); returnText = cstring_to_text(formattedDate); pfree(fmtStrC); pfree(formattedDate); /* # Why not pfree(fmtStrArg) (We do pfree fmtStrC though) - it is an input argument - never pfree() an input argument passived via PG_GETARG - because these memory addresses are managed by the executor - only pfree() memory that we explicitly allocate (e.g., the result of text_to_cstring()) */ PG_RETURN_TEXT_P(returnText); } PG_FUNCTION_INFO_V1(bs_date_to_char_lang); /* - similar to the bs_date_to_char function but this also supports Devanagari output Example: SELECT to_char(bs_date_value, 'YYYY/MM/DD', 'dev') */ Datum bs_date_to_char_lang(PG_FUNCTION_ARGS) { BSDateADT date = PG_GETARG_INT32(0); text *fmtStrArg = PG_GETARG_TEXT_PP(1); text *lang = PG_GETARG_TEXT_PP(2); char *fmtStrC = text_to_cstring(fmtStrArg); char *langC = text_to_cstring(lang); DateParts bsDate; char *formattedDate; text *returnText; int year, month, day; j2date(date + POSTGRES_EPOCH_JDATE, &year, &month, &day); bsDate = adToBs(year, month, day); formattedDate = extractDateWithFmtStr(&bsDate, fmtStrC, langC); returnText = cstring_to_text(formattedDate); pfree(fmtStrC); pfree(langC); pfree(formattedDate); /* # Why not pfree(fmtStrArg) (We do pfree fmtStrC though) - it is an input argument - never pfree() an input argument passived via PG_GETARG - because these memory addresses are managed by the executor - only pfree() memory that we explicitly allocate (e.g., the result of text_to_cstring()) */ PG_RETURN_TEXT_P(returnText); } PG_FUNCTION_INFO_V1(current_bs_date); /* This function basically only: - gets the current transaction timestamp (by making use of GetCurrentTimestamp() from PostgreSQL utils/timestamp.h) - extracts the year, month and date from it (by making use of timestamp2tm()) - performs validation (by calling adToBs itself) - gets the Julian day value from the year, month and date - returns the Julian day int - the actual conversion happens in the bs_date_out function because the SQL function that makes use of this function has a return type 'bs_date' */ Datum current_bs_date(PG_FUNCTION_ARGS) { /* GetCurrentTimestamp() returns the current time (int64 representation) at the start of the transaction - meaning, multiple calls to this function 'current_bs_date' within the same transaction will return consistent result - which is why we prefer this instead of using the builtin C time() and localtime_r() functions based implementation */ TimestampTz now = GetCurrentTimestamp(); /* the only variable we're concerned with here is 'tm' all of the other variables are passed (instead of NULL) because timestamp2tm is a low-level C function that unconditionally attempts to write the results to the memory addresses (passed as arguments) Dereferencing a NULL pointer to write a value would cause segmentatino fault, which would crash the entire PostgreSQL backend process */ int tz; struct pg_tm tm; fsec_t fsec; const char *tzn; DateADT adDate; if (timestamp2tm(now, &tz, &tm, &fsec, &tzn, NULL) != 0) // timestamp2tm returns 0 on success ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); /* subtracting the Postgres Epoch (2000-01-01) to match the internal DateADT representation. */ adDate = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; adToBs(tm.tm_year, tm.tm_mon, tm.tm_mday); // done in order to trigger all of the valdations PG_RETURN_INT32(adDate); }